@medialane/sdk 0.105.0 → 0.107.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.d.cts CHANGED
@@ -497,12 +497,60 @@ declare const ALLOWED_IMAGE_CONTENT_TYPES: Set<string>;
497
497
  declare const MAX_IMAGE_REDIRECTS = 5;
498
498
  declare const MAX_IMAGE_PROXY_BYTES: number;
499
499
  declare function isPrivateHost(hostname: string): boolean;
500
- declare function validateUrl(raw: string): {
500
+ /**
501
+ * Validates a URL before it is fetched server-side. `requireHttps` is on by
502
+ * default; callers that legitimately accept plain http (a metadata URI already
503
+ * committed on chain, say) opt out explicitly rather than the guard being
504
+ * lenient for everyone.
505
+ */
506
+ declare function validateUrl(raw: string, options?: {
507
+ requireHttps?: boolean;
508
+ }): {
501
509
  url: URL;
502
510
  } | {
503
511
  error: string;
504
512
  status: number;
505
513
  };
514
+ /** Convenience for callers that only need a yes/no, mirroring the backend's prior helper. */
515
+ declare function isPrivateOrInsecureUrl(raw: string, requireHttps?: boolean): boolean;
516
+
517
+ /**
518
+ * Proxies a remote image, which means fetching a URL an untrusted caller chose
519
+ * — the classic SSRF shape. Guarded in four places, each of which has to hold:
520
+ *
521
+ * 1. the URL is https, credential-free and not a private host
522
+ * 2. the hostname's *resolved* addresses are re-checked, since a public name
523
+ * can resolve to a private address
524
+ * 3. redirects are followed manually, re-validating each hop, because a
525
+ * permitted host can redirect into the private range
526
+ * 4. the response must be an allowed image type and is read under a byte cap
527
+ *
528
+ * This existed as a byte-identical copy in each app, so a fix to one would not
529
+ * have reached the other. DNS resolution is injected rather than imported: it
530
+ * is runtime-specific, and this package stays isomorphic.
531
+ *
532
+ * Note the remaining exposure: between resolving a hostname and fetching it,
533
+ * the name could resolve differently (DNS rebinding). Closing that needs the
534
+ * fetch pinned to the address already checked, which the platform fetch does
535
+ * not expose. The content-type allowlist and byte cap bound what an attacker
536
+ * gets if they win that race.
537
+ */
538
+ interface ImageProxyConfig {
539
+ checkRateLimit: (ip: string) => boolean;
540
+ /** Returns every address a hostname resolves to. Injected; see above. */
541
+ resolveHostname: (hostname: string) => Promise<string[]>;
542
+ fetchImpl?: typeof fetch;
543
+ userAgent?: string;
544
+ }
545
+ interface CappedBody {
546
+ ok: boolean;
547
+ body?: Uint8Array;
548
+ error?: string;
549
+ status?: number;
550
+ }
551
+ /** Reads a response body, refusing anything past `maxBytes` rather than buffering it. */
552
+ declare function readBodyWithCap(res: Response, maxBytes: number): Promise<CappedBody>;
553
+ declare function createImageProxyHandler(config: ImageProxyConfig): (req: Request) => Promise<Response>;
506
554
 
507
555
  interface BackendMetadataConfig {
508
556
  backendUrl: string;
@@ -525,8 +573,20 @@ declare function uploadDirectoryToBackend(config: BackendMetadataConfig, files:
525
573
  declare function getBackendSignedUrl(config: BackendMetadataConfig, kind?: "image" | "document" | "media"): Promise<string>;
526
574
 
527
575
  declare function createRateLimiter(windowMs: number, max: number): (key: string) => boolean;
576
+ declare const TRUSTED_APP_IP_HEADER = "x-medialane-client-ip";
528
577
  declare function requestIp(req: Request): string;
529
578
 
579
+ /**
580
+ * Same-origin guard for server routes. A missing `Origin` header is allowed,
581
+ * because same-origin navigations and server-to-server calls do not send one;
582
+ * a present `Origin` must match `Host`.
583
+ *
584
+ * Read that carefully before relying on it: this blocks cross-site *browser*
585
+ * abuse, and nothing else. A non-browser client simply omits `Origin` and
586
+ * passes. It is not a substitute for authentication, and on a route that
587
+ * spends money or credits it must be paired with a rate limit and, where the
588
+ * route touches a paid resource, real auth.
589
+ */
530
590
  declare function isSameOrigin(req: Request): boolean;
531
591
 
532
592
  declare const IDENTITY_TTL_SECONDS = 86400;
@@ -596,4 +656,4 @@ declare const IPFS_SAFE_CONTENT_TYPE_PREFIXES: readonly ["image/jpeg", "image/pn
596
656
  declare function resolveSafeImageContentType(contentType: string): string;
597
657
  declare const MAX_IPFS_GATEWAY_RESPONSE_BYTES: number;
598
658
 
599
- export { ACCOUNT_SESSION_TTL_SECONDS, ALLOWED_IMAGE_CONTENT_TYPES, type AssetAttribute, type AssetMetadata, type BackendMetadataConfig, type BackendProxyConfig, type BackendUploadResult, type BuildAssetMetadataInput, type CanonicalHash, Chain, DEFAULT_CURRENCY, type FailoverFetchOptions, type FeeEnv, IDENTITY_TTL_SECONDS, IPFS_SAFE_CONTENT_TYPE_PREFIXES, MAX_IMAGE_PROXY_BYTES, MAX_IMAGE_REDIRECTS, MAX_IPFS_GATEWAY_RESPONSE_BYTES, PAID_UPSTREAM_MARKERS, POLICY_REFUSAL_CODES, PUBLIC_RPC_FALLBACKS, RESERVED_TRAITS, type RemixPolicy, type RemixPolicyInput, ResolvedFeeConfig, type RpcProxyConfig, STARKNET_COLLECTION_1155_CLASS_HASH, STARKNET_COLLECTION_1155_CONTRACT, STARKNET_COLLECTION_1155_FACTORY_CLASS_HASH, STARKNET_COLLECTION_1155_START_BLOCK, STARKNET_COLLECTION_721_CONTRACT, STARKNET_COLLECTION_721_START_BLOCK, STARKNET_CREATOR_COIN_CLASS_HASH, STARKNET_CREATOR_COIN_EKUBO_LAUNCHER, STARKNET_CREATOR_COIN_FACTORY_CLASS_HASH, STARKNET_CREATOR_COIN_FACTORY_CONTRACT, STARKNET_CREATOR_COIN_START_BLOCK, STARKNET_DROP_COLLECTION_CLASS_HASH, STARKNET_DROP_FACTORY_CONTRACT, STARKNET_EKUBO_CORE, STARKNET_GENESIS_MINT_BR_CONTRACT, STARKNET_GENESIS_MINT_GLOBAL_CONTRACT, STARKNET_GENESIS_MINT_LAUNCH_CONTRACT, STARKNET_IPCOLLECTION_CLASS_HASH, STARKNET_IPNFT_CLASS_HASH, STARKNET_IP_CLUB_COLLECTION_CLASS_HASH, STARKNET_IP_CLUB_FACTORY_CONTRACT, STARKNET_IP_SPONSORSHIP_CLASS_HASH, STARKNET_IP_SPONSORSHIP_CONTRACT, STARKNET_IP_TICKETS_FACTORY_CONTRACT, STARKNET_IP_TICKET_COLLECTION_CLASS_HASH, STARKNET_MARKETPLACE_1155_CLASS_HASH, STARKNET_MARKETPLACE_1155_CONTRACT, STARKNET_MARKETPLACE_1155_START_BLOCK, STARKNET_MARKETPLACE_721_CLASS_HASH, STARKNET_MARKETPLACE_721_CONTRACT, STARKNET_MARKETPLACE_721_START_BLOCK, STARKNET_NFTCOMMENTS_CONTRACT, STARKNET_POP_COLLECTION_CLASS_HASH, STARKNET_POP_FACTORY_CONTRACT, SUPPORTED_TOKENS, SUPPORTED_URL_CHAINS, ServiceCapability, ServiceDefinition, type ServiceId, type SiwsIdentity, type SupportedToken, type SupportedTokenSymbol, assetHref, buildAssetMetadata, chainFromSlug, chainSlug, coinHref, collectionHref, createBackendProxyHandler, createFailoverFetch, createRateLimiter, createRpcProxyHandler, encodeU256, formatAmount, getBackendSignedUrl, getDerivativesTerm, getListableTokens, getService, getServicesByCapability, getTokenByAddress, getTokenBySymbol, hasCapability, isPolicyRefusal, isPrivateHost, isSameOrigin, isServiceId, isTransientRpcError, isValidIpfsCidPath, issueAccountSessionToken, issueSiwsToken, listServices, normalizeAddress, normalizeHash, parseAmount, requestIp, resolveAppFeeConfig, resolveRemixPolicy, resolveSafeImageContentType, shortenAddress, stringifyBigInts, u256ToBigInt, uploadDirectoryToBackend, uploadFileToBackend, uploadJsonToBackend, validateUrl, verifyAccountSessionToken, verifySiwsToken };
659
+ export { ACCOUNT_SESSION_TTL_SECONDS, ALLOWED_IMAGE_CONTENT_TYPES, type AssetAttribute, type AssetMetadata, type BackendMetadataConfig, type BackendProxyConfig, type BackendUploadResult, type BuildAssetMetadataInput, type CanonicalHash, type CappedBody, Chain, DEFAULT_CURRENCY, type FailoverFetchOptions, type FeeEnv, IDENTITY_TTL_SECONDS, IPFS_SAFE_CONTENT_TYPE_PREFIXES, type ImageProxyConfig, MAX_IMAGE_PROXY_BYTES, MAX_IMAGE_REDIRECTS, MAX_IPFS_GATEWAY_RESPONSE_BYTES, PAID_UPSTREAM_MARKERS, POLICY_REFUSAL_CODES, PUBLIC_RPC_FALLBACKS, RESERVED_TRAITS, type RemixPolicy, type RemixPolicyInput, ResolvedFeeConfig, type RpcProxyConfig, STARKNET_COLLECTION_1155_CLASS_HASH, STARKNET_COLLECTION_1155_CONTRACT, STARKNET_COLLECTION_1155_FACTORY_CLASS_HASH, STARKNET_COLLECTION_1155_START_BLOCK, STARKNET_COLLECTION_721_CONTRACT, STARKNET_COLLECTION_721_START_BLOCK, STARKNET_CREATOR_COIN_CLASS_HASH, STARKNET_CREATOR_COIN_EKUBO_LAUNCHER, STARKNET_CREATOR_COIN_FACTORY_CLASS_HASH, STARKNET_CREATOR_COIN_FACTORY_CONTRACT, STARKNET_CREATOR_COIN_START_BLOCK, STARKNET_DROP_COLLECTION_CLASS_HASH, STARKNET_DROP_FACTORY_CONTRACT, STARKNET_EKUBO_CORE, STARKNET_GENESIS_MINT_BR_CONTRACT, STARKNET_GENESIS_MINT_GLOBAL_CONTRACT, STARKNET_GENESIS_MINT_LAUNCH_CONTRACT, STARKNET_IPCOLLECTION_CLASS_HASH, STARKNET_IPNFT_CLASS_HASH, STARKNET_IP_CLUB_COLLECTION_CLASS_HASH, STARKNET_IP_CLUB_FACTORY_CONTRACT, STARKNET_IP_SPONSORSHIP_CLASS_HASH, STARKNET_IP_SPONSORSHIP_CONTRACT, STARKNET_IP_TICKETS_FACTORY_CONTRACT, STARKNET_IP_TICKET_COLLECTION_CLASS_HASH, STARKNET_MARKETPLACE_1155_CLASS_HASH, STARKNET_MARKETPLACE_1155_CONTRACT, STARKNET_MARKETPLACE_1155_START_BLOCK, STARKNET_MARKETPLACE_721_CLASS_HASH, STARKNET_MARKETPLACE_721_CONTRACT, STARKNET_MARKETPLACE_721_START_BLOCK, STARKNET_NFTCOMMENTS_CONTRACT, STARKNET_POP_COLLECTION_CLASS_HASH, STARKNET_POP_FACTORY_CONTRACT, SUPPORTED_TOKENS, SUPPORTED_URL_CHAINS, ServiceCapability, ServiceDefinition, type ServiceId, type SiwsIdentity, type SupportedToken, type SupportedTokenSymbol, TRUSTED_APP_IP_HEADER, assetHref, buildAssetMetadata, chainFromSlug, chainSlug, coinHref, collectionHref, createBackendProxyHandler, createFailoverFetch, createImageProxyHandler, createRateLimiter, createRpcProxyHandler, encodeU256, formatAmount, getBackendSignedUrl, getDerivativesTerm, getListableTokens, getService, getServicesByCapability, getTokenByAddress, getTokenBySymbol, hasCapability, isPolicyRefusal, isPrivateHost, isPrivateOrInsecureUrl, isSameOrigin, isServiceId, isTransientRpcError, isValidIpfsCidPath, issueAccountSessionToken, issueSiwsToken, listServices, normalizeAddress, normalizeHash, parseAmount, readBodyWithCap, requestIp, resolveAppFeeConfig, resolveRemixPolicy, resolveSafeImageContentType, shortenAddress, stringifyBigInts, u256ToBigInt, uploadDirectoryToBackend, uploadFileToBackend, uploadJsonToBackend, validateUrl, verifyAccountSessionToken, verifySiwsToken };
package/dist/index.d.ts CHANGED
@@ -497,12 +497,60 @@ declare const ALLOWED_IMAGE_CONTENT_TYPES: Set<string>;
497
497
  declare const MAX_IMAGE_REDIRECTS = 5;
498
498
  declare const MAX_IMAGE_PROXY_BYTES: number;
499
499
  declare function isPrivateHost(hostname: string): boolean;
500
- declare function validateUrl(raw: string): {
500
+ /**
501
+ * Validates a URL before it is fetched server-side. `requireHttps` is on by
502
+ * default; callers that legitimately accept plain http (a metadata URI already
503
+ * committed on chain, say) opt out explicitly rather than the guard being
504
+ * lenient for everyone.
505
+ */
506
+ declare function validateUrl(raw: string, options?: {
507
+ requireHttps?: boolean;
508
+ }): {
501
509
  url: URL;
502
510
  } | {
503
511
  error: string;
504
512
  status: number;
505
513
  };
514
+ /** Convenience for callers that only need a yes/no, mirroring the backend's prior helper. */
515
+ declare function isPrivateOrInsecureUrl(raw: string, requireHttps?: boolean): boolean;
516
+
517
+ /**
518
+ * Proxies a remote image, which means fetching a URL an untrusted caller chose
519
+ * — the classic SSRF shape. Guarded in four places, each of which has to hold:
520
+ *
521
+ * 1. the URL is https, credential-free and not a private host
522
+ * 2. the hostname's *resolved* addresses are re-checked, since a public name
523
+ * can resolve to a private address
524
+ * 3. redirects are followed manually, re-validating each hop, because a
525
+ * permitted host can redirect into the private range
526
+ * 4. the response must be an allowed image type and is read under a byte cap
527
+ *
528
+ * This existed as a byte-identical copy in each app, so a fix to one would not
529
+ * have reached the other. DNS resolution is injected rather than imported: it
530
+ * is runtime-specific, and this package stays isomorphic.
531
+ *
532
+ * Note the remaining exposure: between resolving a hostname and fetching it,
533
+ * the name could resolve differently (DNS rebinding). Closing that needs the
534
+ * fetch pinned to the address already checked, which the platform fetch does
535
+ * not expose. The content-type allowlist and byte cap bound what an attacker
536
+ * gets if they win that race.
537
+ */
538
+ interface ImageProxyConfig {
539
+ checkRateLimit: (ip: string) => boolean;
540
+ /** Returns every address a hostname resolves to. Injected; see above. */
541
+ resolveHostname: (hostname: string) => Promise<string[]>;
542
+ fetchImpl?: typeof fetch;
543
+ userAgent?: string;
544
+ }
545
+ interface CappedBody {
546
+ ok: boolean;
547
+ body?: Uint8Array;
548
+ error?: string;
549
+ status?: number;
550
+ }
551
+ /** Reads a response body, refusing anything past `maxBytes` rather than buffering it. */
552
+ declare function readBodyWithCap(res: Response, maxBytes: number): Promise<CappedBody>;
553
+ declare function createImageProxyHandler(config: ImageProxyConfig): (req: Request) => Promise<Response>;
506
554
 
507
555
  interface BackendMetadataConfig {
508
556
  backendUrl: string;
@@ -525,8 +573,20 @@ declare function uploadDirectoryToBackend(config: BackendMetadataConfig, files:
525
573
  declare function getBackendSignedUrl(config: BackendMetadataConfig, kind?: "image" | "document" | "media"): Promise<string>;
526
574
 
527
575
  declare function createRateLimiter(windowMs: number, max: number): (key: string) => boolean;
576
+ declare const TRUSTED_APP_IP_HEADER = "x-medialane-client-ip";
528
577
  declare function requestIp(req: Request): string;
529
578
 
579
+ /**
580
+ * Same-origin guard for server routes. A missing `Origin` header is allowed,
581
+ * because same-origin navigations and server-to-server calls do not send one;
582
+ * a present `Origin` must match `Host`.
583
+ *
584
+ * Read that carefully before relying on it: this blocks cross-site *browser*
585
+ * abuse, and nothing else. A non-browser client simply omits `Origin` and
586
+ * passes. It is not a substitute for authentication, and on a route that
587
+ * spends money or credits it must be paired with a rate limit and, where the
588
+ * route touches a paid resource, real auth.
589
+ */
530
590
  declare function isSameOrigin(req: Request): boolean;
531
591
 
532
592
  declare const IDENTITY_TTL_SECONDS = 86400;
@@ -596,4 +656,4 @@ declare const IPFS_SAFE_CONTENT_TYPE_PREFIXES: readonly ["image/jpeg", "image/pn
596
656
  declare function resolveSafeImageContentType(contentType: string): string;
597
657
  declare const MAX_IPFS_GATEWAY_RESPONSE_BYTES: number;
598
658
 
599
- export { ACCOUNT_SESSION_TTL_SECONDS, ALLOWED_IMAGE_CONTENT_TYPES, type AssetAttribute, type AssetMetadata, type BackendMetadataConfig, type BackendProxyConfig, type BackendUploadResult, type BuildAssetMetadataInput, type CanonicalHash, Chain, DEFAULT_CURRENCY, type FailoverFetchOptions, type FeeEnv, IDENTITY_TTL_SECONDS, IPFS_SAFE_CONTENT_TYPE_PREFIXES, MAX_IMAGE_PROXY_BYTES, MAX_IMAGE_REDIRECTS, MAX_IPFS_GATEWAY_RESPONSE_BYTES, PAID_UPSTREAM_MARKERS, POLICY_REFUSAL_CODES, PUBLIC_RPC_FALLBACKS, RESERVED_TRAITS, type RemixPolicy, type RemixPolicyInput, ResolvedFeeConfig, type RpcProxyConfig, STARKNET_COLLECTION_1155_CLASS_HASH, STARKNET_COLLECTION_1155_CONTRACT, STARKNET_COLLECTION_1155_FACTORY_CLASS_HASH, STARKNET_COLLECTION_1155_START_BLOCK, STARKNET_COLLECTION_721_CONTRACT, STARKNET_COLLECTION_721_START_BLOCK, STARKNET_CREATOR_COIN_CLASS_HASH, STARKNET_CREATOR_COIN_EKUBO_LAUNCHER, STARKNET_CREATOR_COIN_FACTORY_CLASS_HASH, STARKNET_CREATOR_COIN_FACTORY_CONTRACT, STARKNET_CREATOR_COIN_START_BLOCK, STARKNET_DROP_COLLECTION_CLASS_HASH, STARKNET_DROP_FACTORY_CONTRACT, STARKNET_EKUBO_CORE, STARKNET_GENESIS_MINT_BR_CONTRACT, STARKNET_GENESIS_MINT_GLOBAL_CONTRACT, STARKNET_GENESIS_MINT_LAUNCH_CONTRACT, STARKNET_IPCOLLECTION_CLASS_HASH, STARKNET_IPNFT_CLASS_HASH, STARKNET_IP_CLUB_COLLECTION_CLASS_HASH, STARKNET_IP_CLUB_FACTORY_CONTRACT, STARKNET_IP_SPONSORSHIP_CLASS_HASH, STARKNET_IP_SPONSORSHIP_CONTRACT, STARKNET_IP_TICKETS_FACTORY_CONTRACT, STARKNET_IP_TICKET_COLLECTION_CLASS_HASH, STARKNET_MARKETPLACE_1155_CLASS_HASH, STARKNET_MARKETPLACE_1155_CONTRACT, STARKNET_MARKETPLACE_1155_START_BLOCK, STARKNET_MARKETPLACE_721_CLASS_HASH, STARKNET_MARKETPLACE_721_CONTRACT, STARKNET_MARKETPLACE_721_START_BLOCK, STARKNET_NFTCOMMENTS_CONTRACT, STARKNET_POP_COLLECTION_CLASS_HASH, STARKNET_POP_FACTORY_CONTRACT, SUPPORTED_TOKENS, SUPPORTED_URL_CHAINS, ServiceCapability, ServiceDefinition, type ServiceId, type SiwsIdentity, type SupportedToken, type SupportedTokenSymbol, assetHref, buildAssetMetadata, chainFromSlug, chainSlug, coinHref, collectionHref, createBackendProxyHandler, createFailoverFetch, createRateLimiter, createRpcProxyHandler, encodeU256, formatAmount, getBackendSignedUrl, getDerivativesTerm, getListableTokens, getService, getServicesByCapability, getTokenByAddress, getTokenBySymbol, hasCapability, isPolicyRefusal, isPrivateHost, isSameOrigin, isServiceId, isTransientRpcError, isValidIpfsCidPath, issueAccountSessionToken, issueSiwsToken, listServices, normalizeAddress, normalizeHash, parseAmount, requestIp, resolveAppFeeConfig, resolveRemixPolicy, resolveSafeImageContentType, shortenAddress, stringifyBigInts, u256ToBigInt, uploadDirectoryToBackend, uploadFileToBackend, uploadJsonToBackend, validateUrl, verifyAccountSessionToken, verifySiwsToken };
659
+ export { ACCOUNT_SESSION_TTL_SECONDS, ALLOWED_IMAGE_CONTENT_TYPES, type AssetAttribute, type AssetMetadata, type BackendMetadataConfig, type BackendProxyConfig, type BackendUploadResult, type BuildAssetMetadataInput, type CanonicalHash, type CappedBody, Chain, DEFAULT_CURRENCY, type FailoverFetchOptions, type FeeEnv, IDENTITY_TTL_SECONDS, IPFS_SAFE_CONTENT_TYPE_PREFIXES, type ImageProxyConfig, MAX_IMAGE_PROXY_BYTES, MAX_IMAGE_REDIRECTS, MAX_IPFS_GATEWAY_RESPONSE_BYTES, PAID_UPSTREAM_MARKERS, POLICY_REFUSAL_CODES, PUBLIC_RPC_FALLBACKS, RESERVED_TRAITS, type RemixPolicy, type RemixPolicyInput, ResolvedFeeConfig, type RpcProxyConfig, STARKNET_COLLECTION_1155_CLASS_HASH, STARKNET_COLLECTION_1155_CONTRACT, STARKNET_COLLECTION_1155_FACTORY_CLASS_HASH, STARKNET_COLLECTION_1155_START_BLOCK, STARKNET_COLLECTION_721_CONTRACT, STARKNET_COLLECTION_721_START_BLOCK, STARKNET_CREATOR_COIN_CLASS_HASH, STARKNET_CREATOR_COIN_EKUBO_LAUNCHER, STARKNET_CREATOR_COIN_FACTORY_CLASS_HASH, STARKNET_CREATOR_COIN_FACTORY_CONTRACT, STARKNET_CREATOR_COIN_START_BLOCK, STARKNET_DROP_COLLECTION_CLASS_HASH, STARKNET_DROP_FACTORY_CONTRACT, STARKNET_EKUBO_CORE, STARKNET_GENESIS_MINT_BR_CONTRACT, STARKNET_GENESIS_MINT_GLOBAL_CONTRACT, STARKNET_GENESIS_MINT_LAUNCH_CONTRACT, STARKNET_IPCOLLECTION_CLASS_HASH, STARKNET_IPNFT_CLASS_HASH, STARKNET_IP_CLUB_COLLECTION_CLASS_HASH, STARKNET_IP_CLUB_FACTORY_CONTRACT, STARKNET_IP_SPONSORSHIP_CLASS_HASH, STARKNET_IP_SPONSORSHIP_CONTRACT, STARKNET_IP_TICKETS_FACTORY_CONTRACT, STARKNET_IP_TICKET_COLLECTION_CLASS_HASH, STARKNET_MARKETPLACE_1155_CLASS_HASH, STARKNET_MARKETPLACE_1155_CONTRACT, STARKNET_MARKETPLACE_1155_START_BLOCK, STARKNET_MARKETPLACE_721_CLASS_HASH, STARKNET_MARKETPLACE_721_CONTRACT, STARKNET_MARKETPLACE_721_START_BLOCK, STARKNET_NFTCOMMENTS_CONTRACT, STARKNET_POP_COLLECTION_CLASS_HASH, STARKNET_POP_FACTORY_CONTRACT, SUPPORTED_TOKENS, SUPPORTED_URL_CHAINS, ServiceCapability, ServiceDefinition, type ServiceId, type SiwsIdentity, type SupportedToken, type SupportedTokenSymbol, TRUSTED_APP_IP_HEADER, assetHref, buildAssetMetadata, chainFromSlug, chainSlug, coinHref, collectionHref, createBackendProxyHandler, createFailoverFetch, createImageProxyHandler, createRateLimiter, createRpcProxyHandler, encodeU256, formatAmount, getBackendSignedUrl, getDerivativesTerm, getListableTokens, getService, getServicesByCapability, getTokenByAddress, getTokenBySymbol, hasCapability, isPolicyRefusal, isPrivateHost, isPrivateOrInsecureUrl, isSameOrigin, isServiceId, isTransientRpcError, isValidIpfsCidPath, issueAccountSessionToken, issueSiwsToken, listServices, normalizeAddress, normalizeHash, parseAmount, readBodyWithCap, requestIp, resolveAppFeeConfig, resolveRemixPolicy, resolveSafeImageContentType, shortenAddress, stringifyBigInts, u256ToBigInt, uploadDirectoryToBackend, uploadFileToBackend, uploadJsonToBackend, validateUrl, verifyAccountSessionToken, verifySiwsToken };
package/dist/index.js CHANGED
@@ -1375,8 +1375,19 @@ function createRateLimiter(windowMs, max) {
1375
1375
  return true;
1376
1376
  };
1377
1377
  }
1378
+ var TRUSTED_APP_IP_HEADER = "x-medialane-client-ip";
1378
1379
  function requestIp(req) {
1379
- return req.headers.get("x-forwarded-for")?.split(",")[0].trim() ?? "unknown";
1380
+ const fromApp = req.headers.get(TRUSTED_APP_IP_HEADER)?.trim();
1381
+ if (fromApp) return fromApp;
1382
+ const fromEdge = req.headers.get("x-vercel-forwarded-for")?.split(",")[0]?.trim();
1383
+ if (fromEdge) return fromEdge;
1384
+ const forwarded = req.headers.get("x-forwarded-for");
1385
+ if (forwarded) {
1386
+ const hops = forwarded.split(",").map((hop) => hop.trim()).filter(Boolean);
1387
+ const nearest = hops[hops.length - 1];
1388
+ if (nearest) return nearest;
1389
+ }
1390
+ return "unknown";
1380
1391
  }
1381
1392
 
1382
1393
  // src/server/origin.ts
@@ -1465,43 +1476,135 @@ var ALLOWED_IMAGE_CONTENT_TYPES = /* @__PURE__ */ new Set([
1465
1476
  ]);
1466
1477
  var MAX_IMAGE_REDIRECTS = 5;
1467
1478
  var MAX_IMAGE_PROXY_BYTES = 15 * 1024 * 1024;
1479
+ function parseIpv4(ip) {
1480
+ const parts = ip.split(".");
1481
+ if (parts.length !== 4) return null;
1482
+ const bytes = [];
1483
+ for (const part of parts) {
1484
+ if (!/^\d{1,3}$/.test(part)) return null;
1485
+ const n = Number(part);
1486
+ if (n > 255) return null;
1487
+ bytes.push(n);
1488
+ }
1489
+ return bytes;
1490
+ }
1491
+ function isPrivateIpv4(bytes) {
1492
+ const a = bytes[0];
1493
+ const b = bytes[1];
1494
+ const c = bytes[2];
1495
+ if (a === 0) return true;
1496
+ if (a === 10) return true;
1497
+ if (a === 100 && b >= 64 && b <= 127) return true;
1498
+ if (a === 127) return true;
1499
+ if (a === 169 && b === 254) return true;
1500
+ if (a === 172 && b >= 16 && b <= 31) return true;
1501
+ if (a === 192 && b === 0 && c === 0) return true;
1502
+ if (a === 192 && b === 168) return true;
1503
+ if (a === 198 && (b === 18 || b === 19)) return true;
1504
+ if (a >= 224) return true;
1505
+ return false;
1506
+ }
1507
+ function expandIpv6(rawIp) {
1508
+ let ip = rawIp;
1509
+ let embeddedV4 = null;
1510
+ const v4Tail = ip.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
1511
+ if (v4Tail && ip.includes(":")) {
1512
+ embeddedV4 = parseIpv4(v4Tail[1]);
1513
+ if (!embeddedV4) return null;
1514
+ ip = ip.slice(0, ip.length - v4Tail[1].length) + "0:0";
1515
+ }
1516
+ const sides = ip.split("::");
1517
+ if (sides.length > 2) return null;
1518
+ const head = sides[0] ? sides[0].split(":").filter(Boolean) : [];
1519
+ const tail = sides.length === 2 && sides[1] ? sides[1].split(":").filter(Boolean) : [];
1520
+ let groups;
1521
+ if (sides.length === 1) {
1522
+ groups = head;
1523
+ if (groups.length !== 8) return null;
1524
+ } else {
1525
+ const missing = 8 - head.length - tail.length;
1526
+ if (missing < 0) return null;
1527
+ groups = [...head, ...Array(missing).fill("0"), ...tail];
1528
+ }
1529
+ if (groups.length !== 8) return null;
1530
+ const bytes = [];
1531
+ for (const group of groups) {
1532
+ if (!/^[0-9a-fA-F]{1,4}$/.test(group)) return null;
1533
+ const n = parseInt(group, 16);
1534
+ bytes.push(n >> 8 & 255, n & 255);
1535
+ }
1536
+ if (embeddedV4) {
1537
+ bytes[12] = embeddedV4[0];
1538
+ bytes[13] = embeddedV4[1];
1539
+ bytes[14] = embeddedV4[2];
1540
+ bytes[15] = embeddedV4[3];
1541
+ }
1542
+ return bytes;
1543
+ }
1544
+ function isPrivateIpv6(bytes) {
1545
+ if (bytes.every((b) => b === 0)) return true;
1546
+ if (bytes.slice(0, 15).every((b) => b === 0) && bytes[15] === 1) return true;
1547
+ if ((bytes[0] & 254) === 252) return true;
1548
+ if (bytes[0] === 254 && (bytes[1] & 192) === 128) return true;
1549
+ if (bytes.slice(0, 10).every((b) => b === 0) && bytes[10] === 255 && bytes[11] === 255) {
1550
+ return isPrivateIpv4(bytes.slice(12));
1551
+ }
1552
+ return false;
1553
+ }
1554
+ function parseNumber(part) {
1555
+ if (/^0x[0-9a-f]+$/i.test(part)) return parseInt(part.slice(2), 16);
1556
+ if (/^0[0-7]+$/.test(part)) return parseInt(part, 8);
1557
+ if (/^\d+$/.test(part)) return parseInt(part, 10);
1558
+ return null;
1559
+ }
1560
+ function normalizeNumericHostname(host) {
1561
+ const parts = host.trim().split(".");
1562
+ if (parts.length < 1 || parts.length > 4) return null;
1563
+ const values = [];
1564
+ for (const part of parts) {
1565
+ const value = parseNumber(part);
1566
+ if (value === null || value < 0) return null;
1567
+ values.push(value);
1568
+ }
1569
+ const leading = values.slice(0, -1);
1570
+ if (leading.some((v) => v > 255)) return null;
1571
+ const tail = values[values.length - 1];
1572
+ const tailBytes = 4 - leading.length;
1573
+ if (tail > 2 ** (8 * tailBytes) - 1) return null;
1574
+ const bytes = [...leading];
1575
+ for (let i = tailBytes - 1; i >= 0; i--) bytes.push(tail >>> 8 * i & 255);
1576
+ return bytes.join(".");
1577
+ }
1578
+ var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
1579
+ "localhost",
1580
+ "metadata.google.internal",
1581
+ "metadata.azure.internal"
1582
+ ]);
1468
1583
  function isPrivateHost(hostname) {
1469
- const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
1470
- if (h === "localhost" || h === "127.0.0.1" || h === "0.0.0.0") return true;
1471
- if (/^\d+$/.test(h)) {
1472
- const n = parseInt(h, 10);
1473
- if (n === 2130706433 || n === 0 || n >= 2886729728 && n <= 2887778303 || n >= 3232235520 && n <= 3232301055 || n >= 167772160 && n <= 184549375 || n >= 2851995648 && n <= 2852061183) return true;
1474
- }
1475
- if (/^0x[0-9a-f]+$/i.test(h)) {
1476
- const n = parseInt(h, 16);
1477
- if (n === 2130706433 || n === 0 || n >= 2886729728 && n <= 2887778303 || n >= 3232235520 && n <= 3232301055 || n >= 167772160 && n <= 184549375 || n >= 2851995648 && n <= 2852061183) return true;
1478
- }
1479
- if (/^0\d+\.\d+\.\d+\.\d+$/.test(h)) return true;
1480
- if (h === "::1" || /^0*:0*:0*:0*:0*:0*:0*:0*1$/.test(h)) return true;
1481
- const v4mapped = h.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
1482
- if (v4mapped) return isPrivateHost(v4mapped[1]);
1483
- if (/^10\./.test(h)) return true;
1484
- if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
1485
- if (/^192\.168\./.test(h)) return true;
1486
- if (/^169\.254\./.test(h)) return true;
1487
- if (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(h)) return true;
1488
- if (/^fe80:/i.test(h)) return true;
1489
- if (/^f[cd][0-9a-f]{2}:/i.test(h)) return true;
1490
- if (h.endsWith(".local")) return true;
1491
- if (h === "metadata.google.internal") return true;
1492
- if (h === "metadata.azure.internal") return true;
1584
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
1585
+ if (BLOCKED_HOSTNAMES.has(host)) return true;
1586
+ if (host.endsWith(".local") || host.endsWith(".internal")) return true;
1587
+ const candidate = normalizeNumericHostname(host) ?? host;
1588
+ const v4 = parseIpv4(candidate);
1589
+ if (v4) return isPrivateIpv4(v4);
1590
+ const v6 = expandIpv6(candidate);
1591
+ if (v6) return isPrivateIpv6(v6);
1493
1592
  return false;
1494
1593
  }
1495
- function validateUrl(raw) {
1594
+ function validateUrl(raw, options = {}) {
1595
+ const requireHttps = options.requireHttps ?? true;
1496
1596
  let parsed;
1497
1597
  try {
1498
1598
  parsed = new URL(raw);
1499
1599
  } catch {
1500
1600
  return { error: "Invalid url", status: 400 };
1501
1601
  }
1502
- if (parsed.protocol !== "https:") {
1602
+ if (requireHttps && parsed.protocol !== "https:") {
1503
1603
  return { error: "Only https URLs allowed", status: 400 };
1504
1604
  }
1605
+ if (!requireHttps && parsed.protocol !== "https:" && parsed.protocol !== "http:") {
1606
+ return { error: "Only http and https URLs allowed", status: 400 };
1607
+ }
1505
1608
  if (parsed.username || parsed.password) {
1506
1609
  return { error: "URL credentials not allowed", status: 400 };
1507
1610
  }
@@ -1510,6 +1613,111 @@ function validateUrl(raw) {
1510
1613
  }
1511
1614
  return { url: parsed };
1512
1615
  }
1616
+ function isPrivateOrInsecureUrl(raw, requireHttps = true) {
1617
+ return "error" in validateUrl(raw, { requireHttps });
1618
+ }
1619
+
1620
+ // src/server/image-proxy.ts
1621
+ var DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; Medialane/1.0; +https://www.medialane.io)";
1622
+ function jsonError(message, status) {
1623
+ return Response.json({ error: message }, { status });
1624
+ }
1625
+ async function readBodyWithCap(res, maxBytes) {
1626
+ const declared = Number(res.headers.get("content-length") ?? 0);
1627
+ if (declared > maxBytes) {
1628
+ return { ok: false, error: "Image too large", status: 413 };
1629
+ }
1630
+ const reader = res.body?.getReader();
1631
+ if (!reader) return { ok: false, error: "Empty response body", status: 502 };
1632
+ const chunks = [];
1633
+ let total = 0;
1634
+ for (; ; ) {
1635
+ const { done, value } = await reader.read();
1636
+ if (done) break;
1637
+ if (!value) continue;
1638
+ total += value.byteLength;
1639
+ if (total > maxBytes) {
1640
+ await reader.cancel().catch(() => {
1641
+ });
1642
+ return { ok: false, error: "Image too large", status: 413 };
1643
+ }
1644
+ chunks.push(value);
1645
+ }
1646
+ const body = new Uint8Array(total);
1647
+ let offset = 0;
1648
+ for (const chunk of chunks) {
1649
+ body.set(chunk, offset);
1650
+ offset += chunk.byteLength;
1651
+ }
1652
+ return { ok: true, body };
1653
+ }
1654
+ function createImageProxyHandler(config) {
1655
+ const doFetch = config.fetchImpl ?? fetch;
1656
+ const userAgent = config.userAgent ?? DEFAULT_USER_AGENT;
1657
+ async function resolvesToPrivateHost(hostname) {
1658
+ try {
1659
+ const addresses = await config.resolveHostname(hostname);
1660
+ if (addresses.length === 0) return true;
1661
+ return addresses.some((address) => isPrivateHost(address));
1662
+ } catch {
1663
+ return true;
1664
+ }
1665
+ }
1666
+ async function safeFetch(url, hopsLeft) {
1667
+ if (hopsLeft < 0) throw new Error("Too many redirects");
1668
+ if (await resolvesToPrivateHost(url.hostname)) {
1669
+ throw new Error("Blocked: hostname resolves to a private address");
1670
+ }
1671
+ const res = await doFetch(url.toString(), {
1672
+ redirect: "manual",
1673
+ headers: { "User-Agent": userAgent }
1674
+ });
1675
+ if (res.status >= 300 && res.status < 400) {
1676
+ const location = res.headers.get("location");
1677
+ if (!location) throw new Error("Redirect with no Location header");
1678
+ const next = new URL(location, url);
1679
+ const validated = validateUrl(next.toString());
1680
+ if ("error" in validated) throw new Error(`Redirect blocked: ${validated.error}`);
1681
+ return safeFetch(validated.url, hopsLeft - 1);
1682
+ }
1683
+ return res;
1684
+ }
1685
+ return async function handleImageProxy(req) {
1686
+ if (!config.checkRateLimit(requestIp(req))) {
1687
+ return jsonError("Too many requests", 429);
1688
+ }
1689
+ const raw = new URL(req.url).searchParams.get("url");
1690
+ if (!raw) return jsonError("Missing url", 400);
1691
+ const validated = validateUrl(raw);
1692
+ if ("error" in validated) return jsonError(validated.error, validated.status);
1693
+ let upstream;
1694
+ try {
1695
+ upstream = await safeFetch(validated.url, MAX_IMAGE_REDIRECTS);
1696
+ } catch {
1697
+ return jsonError("Failed to fetch image", 502);
1698
+ }
1699
+ if (!upstream.ok) {
1700
+ return jsonError(`Upstream returned ${upstream.status}`, upstream.status);
1701
+ }
1702
+ const contentType = upstream.headers.get("content-type") ?? "";
1703
+ const baseType = contentType.split(";")[0].trim().toLowerCase();
1704
+ if (!ALLOWED_IMAGE_CONTENT_TYPES.has(baseType)) {
1705
+ return jsonError("Not an image", 400);
1706
+ }
1707
+ const capped = await readBodyWithCap(upstream, MAX_IMAGE_PROXY_BYTES);
1708
+ if (!capped.ok) return jsonError(capped.error, capped.status);
1709
+ return new Response(capped.body, {
1710
+ status: 200,
1711
+ headers: {
1712
+ "Content-Type": contentType,
1713
+ "X-Content-Type-Options": "nosniff",
1714
+ "Content-Security-Policy": "default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; sandbox",
1715
+ "Cache-Control": "public, max-age=86400, s-maxage=86400, stale-while-revalidate=604800",
1716
+ "Access-Control-Allow-Origin": "*"
1717
+ }
1718
+ });
1719
+ };
1720
+ }
1513
1721
 
1514
1722
  // src/server/backend-metadata.ts
1515
1723
  function endpoint(config, path) {
@@ -1728,6 +1936,6 @@ function resolveSafeImageContentType(contentType) {
1728
1936
  }
1729
1937
  var MAX_IPFS_GATEWAY_RESPONSE_BYTES = 25 * 1024 * 1024;
1730
1938
 
1731
- export { ACCOUNT_SESSION_TTL_SECONDS, ALLOWED_IMAGE_CONTENT_TYPES, ApiClient, CHAINS, DEFAULT_CHAIN, DEFAULT_CURRENCY, FeeConfigSchema, IDENTITY_TTL_SECONDS, IPFS_SAFE_CONTENT_TYPE_PREFIXES, MAX_IMAGE_PROXY_BYTES, MAX_IMAGE_REDIRECTS, MAX_IPFS_GATEWAY_RESPONSE_BYTES, MedialaneApiError, OPEN_LICENSES, PAID_UPSTREAM_MARKERS, POLICY_REFUSAL_CODES, PUBLIC_RPC_FALLBACKS, RESERVED_TRAITS, STARKNET_COLLECTION_1155_CLASS_HASH, STARKNET_COLLECTION_1155_CONTRACT, STARKNET_COLLECTION_1155_FACTORY_CLASS_HASH, STARKNET_COLLECTION_1155_START_BLOCK, STARKNET_COLLECTION_721_CONTRACT, STARKNET_COLLECTION_721_START_BLOCK, STARKNET_CREATOR_COIN_CLASS_HASH, STARKNET_CREATOR_COIN_EKUBO_LAUNCHER, STARKNET_CREATOR_COIN_FACTORY_CLASS_HASH, STARKNET_CREATOR_COIN_FACTORY_CONTRACT, STARKNET_CREATOR_COIN_START_BLOCK, STARKNET_DROP_COLLECTION_CLASS_HASH, STARKNET_DROP_FACTORY_CONTRACT, STARKNET_EKUBO_CORE, STARKNET_GENESIS_MINT_BR_CONTRACT, STARKNET_GENESIS_MINT_GLOBAL_CONTRACT, STARKNET_GENESIS_MINT_LAUNCH_CONTRACT, STARKNET_IPCOLLECTION_CLASS_HASH, STARKNET_IPNFT_CLASS_HASH, STARKNET_IP_CLUB_COLLECTION_CLASS_HASH, STARKNET_IP_CLUB_FACTORY_CONTRACT, STARKNET_IP_SPONSORSHIP_CLASS_HASH, STARKNET_IP_SPONSORSHIP_CONTRACT, STARKNET_IP_TICKETS_FACTORY_CONTRACT, STARKNET_IP_TICKET_COLLECTION_CLASS_HASH, STARKNET_MARKETPLACE_1155_CLASS_HASH, STARKNET_MARKETPLACE_1155_CONTRACT, STARKNET_MARKETPLACE_1155_START_BLOCK, STARKNET_MARKETPLACE_721_CLASS_HASH, STARKNET_MARKETPLACE_721_CONTRACT, STARKNET_MARKETPLACE_721_START_BLOCK, STARKNET_NFTCOMMENTS_CONTRACT, STARKNET_POP_COLLECTION_CLASS_HASH, STARKNET_POP_FACTORY_CONTRACT, SUPPORTED_TOKENS, SUPPORTED_URL_CHAINS, assetHref, buildAssetMetadata, chainFromSlug, chainSlug, coinHref, collectionHref, createBackendProxyHandler, createFailoverFetch, createRateLimiter, createRpcProxyHandler, encodeU256, formatAmount, getBackendSignedUrl, getCoordinates, getDerivativesTerm, getListableTokens, getService, getServicesByCapability, getStarknetCoordinates, getTokenByAddress, getTokenBySymbol, hasCapability, isPolicyRefusal, isPrivateHost, isSameOrigin, isServiceId, isTransientRpcError, isValidIpfsCidPath, issueAccountSessionToken, issueSiwsToken, listServices, normalizeAddress, normalizeHash, parseAmount, requestIp, resolveAppFeeConfig, resolveConfig, resolveFeeConfig, resolveRemixPolicy, resolveSafeImageContentType, shortenAddress, stringifyBigInts, u256ToBigInt, uploadDirectoryToBackend, uploadFileToBackend, uploadJsonToBackend, validateUrl, verifyAccountSessionToken, verifySiwsToken };
1939
+ export { ACCOUNT_SESSION_TTL_SECONDS, ALLOWED_IMAGE_CONTENT_TYPES, ApiClient, CHAINS, DEFAULT_CHAIN, DEFAULT_CURRENCY, FeeConfigSchema, IDENTITY_TTL_SECONDS, IPFS_SAFE_CONTENT_TYPE_PREFIXES, MAX_IMAGE_PROXY_BYTES, MAX_IMAGE_REDIRECTS, MAX_IPFS_GATEWAY_RESPONSE_BYTES, MedialaneApiError, OPEN_LICENSES, PAID_UPSTREAM_MARKERS, POLICY_REFUSAL_CODES, PUBLIC_RPC_FALLBACKS, RESERVED_TRAITS, STARKNET_COLLECTION_1155_CLASS_HASH, STARKNET_COLLECTION_1155_CONTRACT, STARKNET_COLLECTION_1155_FACTORY_CLASS_HASH, STARKNET_COLLECTION_1155_START_BLOCK, STARKNET_COLLECTION_721_CONTRACT, STARKNET_COLLECTION_721_START_BLOCK, STARKNET_CREATOR_COIN_CLASS_HASH, STARKNET_CREATOR_COIN_EKUBO_LAUNCHER, STARKNET_CREATOR_COIN_FACTORY_CLASS_HASH, STARKNET_CREATOR_COIN_FACTORY_CONTRACT, STARKNET_CREATOR_COIN_START_BLOCK, STARKNET_DROP_COLLECTION_CLASS_HASH, STARKNET_DROP_FACTORY_CONTRACT, STARKNET_EKUBO_CORE, STARKNET_GENESIS_MINT_BR_CONTRACT, STARKNET_GENESIS_MINT_GLOBAL_CONTRACT, STARKNET_GENESIS_MINT_LAUNCH_CONTRACT, STARKNET_IPCOLLECTION_CLASS_HASH, STARKNET_IPNFT_CLASS_HASH, STARKNET_IP_CLUB_COLLECTION_CLASS_HASH, STARKNET_IP_CLUB_FACTORY_CONTRACT, STARKNET_IP_SPONSORSHIP_CLASS_HASH, STARKNET_IP_SPONSORSHIP_CONTRACT, STARKNET_IP_TICKETS_FACTORY_CONTRACT, STARKNET_IP_TICKET_COLLECTION_CLASS_HASH, STARKNET_MARKETPLACE_1155_CLASS_HASH, STARKNET_MARKETPLACE_1155_CONTRACT, STARKNET_MARKETPLACE_1155_START_BLOCK, STARKNET_MARKETPLACE_721_CLASS_HASH, STARKNET_MARKETPLACE_721_CONTRACT, STARKNET_MARKETPLACE_721_START_BLOCK, STARKNET_NFTCOMMENTS_CONTRACT, STARKNET_POP_COLLECTION_CLASS_HASH, STARKNET_POP_FACTORY_CONTRACT, SUPPORTED_TOKENS, SUPPORTED_URL_CHAINS, TRUSTED_APP_IP_HEADER, assetHref, buildAssetMetadata, chainFromSlug, chainSlug, coinHref, collectionHref, createBackendProxyHandler, createFailoverFetch, createImageProxyHandler, createRateLimiter, createRpcProxyHandler, encodeU256, formatAmount, getBackendSignedUrl, getCoordinates, getDerivativesTerm, getListableTokens, getService, getServicesByCapability, getStarknetCoordinates, getTokenByAddress, getTokenBySymbol, hasCapability, isPolicyRefusal, isPrivateHost, isPrivateOrInsecureUrl, isSameOrigin, isServiceId, isTransientRpcError, isValidIpfsCidPath, issueAccountSessionToken, issueSiwsToken, listServices, normalizeAddress, normalizeHash, parseAmount, readBodyWithCap, requestIp, resolveAppFeeConfig, resolveConfig, resolveFeeConfig, resolveRemixPolicy, resolveSafeImageContentType, shortenAddress, stringifyBigInts, u256ToBigInt, uploadDirectoryToBackend, uploadFileToBackend, uploadJsonToBackend, validateUrl, verifyAccountSessionToken, verifySiwsToken };
1732
1940
  //# sourceMappingURL=index.js.map
1733
1941
  //# sourceMappingURL=index.js.map