diff --git a/CHANGELOG.md b/CHANGELOG.md index 80915c6..6c35da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.8.0 + +- Add opt-in, Node-first **x402 payment support**: `x402: true` config preset (routes to + `https://x402.glassnode.com`) and a new `glassnode-api/x402` subpath export with + `createX402Fetch({ account, maxPaymentPerCall })`. The crypto stack (`@x402/fetch`, `@x402/evm`, + `viem`) is an optional peer dependency; the core package stays `zod`-only. +- `apiKey` is now optional when `x402` is enabled; `fetch` is required in that mode. +- Add a friendly `402` error message. Bulk metrics remain free-API only (unsupported over x402). +- `GlassnodeApiError` now surfaces the server's error-body message (e.g. "Resolution 1h is not + allowed") and exposes it on `.detail`, instead of only a generic status message. +- **Security:** redact the `api_key` query-param value in URLs passed to the optional `logger` + (previously the key could leak into log sinks). + ## 0.7.7 - Fix transitive dev-dependency vulnerabilities via `pnpm.overrides`: `flatted` ≥3.4.2 (high), `serialize-javascript` ≥7.0.5, `picomatch` ≥4.0.4, `brace-expansion` ≥5.0.6 — `pnpm audit` now clean diff --git a/CLAUDE.md b/CLAUDE.md index d9085e6..415f0d3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This document provides context for Claude when working with this project. - `/src` - Source code - `/src/types` - TypeScript type definitions (Zod schemas + inferred types) -- `/test` - Test files (Jest) +- `/test` - Test files (Vitest) - `/examples` - Example usage patterns - `/dist` - Compiled output (not checked into git) diff --git a/README.md b/README.md index b3a76d8..2783125 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ const btcPrice = await api.callMetric('/market/price_usd_close', { a: 'BTC' }); - [Error Handling](#error-handling) - [Retries](#retries) - [Bulk Metrics](#bulk-metrics) +- [Paid calls with x402](#paid-calls-with-x402) - [Browser](#browser) - [Examples](#examples) - [Development](#development) @@ -89,16 +90,17 @@ const data = await api.callMetric('/market/price_usd_close', { `new GlassnodeAPI(config)` -| Option | Type | Default | Description | -| ------------ | ----------------------------------------------- | --------------------------- | ------------------------------------------------------- | -| `apiKey` | `string` | — (**required**) | Your Glassnode API key | -| `apiUrl` | `string` | `https://api.glassnode.com` | Base URL for the API | -| `logger` | `(message: string, ...args: unknown[]) => void` | — | Callback for debug logging (e.g. `console.log`) | -| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation (custom headers, testing…) | -| `maxRetries` | `number` | `0` | Retries for retryable errors (`429`, `5xx`) | -| `retryDelay` | `number` | `1000` | Base delay in ms between retries (doubles each attempt) | +| Option | Type | Default | Description | +| ------------ | ----------------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------- | +| `apiKey` | `string` | — (required unless `x402`) | Your Glassnode API key | +| `apiUrl` | `string` | `https://api.glassnode.com` | Base URL for the API | +| `x402` | `boolean` | `false` | Route through the paid x402 endpoint (see [Paid calls with x402](#paid-calls-with-x402)) | +| `logger` | `(message: string, ...args: unknown[]) => void` | — | Callback for debug logging (e.g. `console.log`) | +| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation (or an x402-wrapped fetch) | +| `maxRetries` | `number` | `0` | Retries for retryable errors (`429`, `5xx`) | +| `retryDelay` | `number` | `1000` | Base delay in ms between retries (doubles each attempt) | -The config is validated at construction time with Zod — an invalid config (e.g. an empty `apiKey`) throws immediately. +The config is validated at construction time with Zod — an invalid config (e.g. an empty `apiKey`) throws immediately. When `x402` is enabled, `apiKey` is optional but a payment-capable `fetch` is required. Failed requests throw a `GlassnodeApiError` whose message includes the server's error detail (also on `.detail`). ## Methods @@ -156,6 +158,56 @@ const marketcaps = await api.callBulkMetric('/market/marketcap_usd'); // [{ t: 1609459200, bulk: [{ a: 'BTC', v: 600000000000 }, { a: 'ETH', v: 100000000000 }] }] ``` +## Paid calls with x402 + +Glassnode also serves a **paid, per-call API over the [x402 protocol](https://x402.org)** at +`https://x402.glassnode.com` — no API key required, you pay per request in USDC on Base +($0.01/metadata call, $0.05/metric call). This is **Node-first** and opt-in: the crypto stack +(`@x402/fetch`, `@x402/evm`, `viem`) is an **optional peer dependency**, installed only if you use it. + +```bash +pnpm add glassnode-api @x402/fetch @x402/evm viem +``` + +```typescript +import { GlassnodeAPI } from 'glassnode-api'; +import { createX402Fetch } from 'glassnode-api/x402'; +import { privateKeyToAccount } from 'viem/accounts'; + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); + +const api = new GlassnodeAPI({ + x402: true, // → https://x402.glassnode.com + fetch: await createX402Fetch({ + account, + maxPaymentPerCall: '0.06', // USDC per-call ceiling (default) + }), +}); + +// Pays $0.05 USDC on Base, transparently: +const mvrv = await api.callMetric('/market/mvrv', { a: 'BTC', i: '24h' }); +``` + +**`createX402Fetch(options)`** + +| Option | Type | Default | Description | +| ------------------- | -------------- | ------------------ | -------------------------------- | +| `account` | `LocalAccount` | — (**required**) | viem account that signs payments | +| `maxPaymentPerCall` | `string` | `'0.06'` | Per-call USDC spend ceiling | +| `fetch` | `typeof fetch` | `globalThis.fetch` | Base fetch to wrap | + +> **Spend safety:** `maxPaymentPerCall` caps a **single** request — it is **not** a cumulative budget, so +> an agent loop can still spend within that ceiling repeatedly. Use a **dedicated, funded-but-limited** +> wallet (never your primary key), and load the key from the environment — never hardcode it. + +**Notes** + +- **Bulk metrics are not available over x402** — `callBulkMetric()` only works against the free + `api.glassnode.com`. +- **Other endpoints:** target a non-default x402 endpoint (e.g. a testnet) by passing its URL as + `apiUrl`. +- **Browser** signing is not supported yet (planned). + ## Browser The library ships prebuilt UMD and ESM bundles, so it also runs directly in the browser diff --git a/docs/superpowers/plans/2026-07-14-x402-payment-support.md b/docs/superpowers/plans/2026-07-14-x402-payment-support.md new file mode 100644 index 0000000..51c5a19 --- /dev/null +++ b/docs/superpowers/plans/2026-07-14-x402-payment-support.md @@ -0,0 +1,777 @@ +# x402 Payment Support Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add opt-in, Node-first x402 payment support to `glassnode-api` so a caller can make paid Glassnode calls, without shipping crypto code in the core package. + +**Architecture:** The core `GlassnodeAPI` gains an `x402` boolean preset (switches the base URL to `https://x402.glassnode.com`) and already accepts an injected `fetch`. A new subpath export `glassnode-api/x402` provides `createX402Fetch()`, which dynamically imports `@x402/fetch` + `@x402/evm` (declared as optional peer dependencies) and returns a payment-capable fetch. The core entry never imports crypto. + +**Tech Stack:** TypeScript 6.0.3, Zod 4, Vitest, Rollup; x402 client `@x402/fetch` v2 + `@x402/evm` (Coinbase-CDP-recommended, x402 protocol v2), `viem` (type-only in the lib; the caller builds the account). + +## Global Constraints + +- **Node ≥ 18**; developed on Node 24; pnpm is the package manager. +- **TypeScript pinned to 6.x** (`^6.0.3`) — do NOT bump to 7 (breaks typescript-eslint). +- **Core package stays `zod`-only at runtime.** `@x402/fetch`, `@x402/evm`, `viem` are **optional peer dependencies** (+ devDependencies for build/test). Never import them from any file other than `src/x402.ts`, and only via dynamic `import()` / `import type`. +- **Verified pricing/protocol (do not re-derive):** endpoints return `x402Version: 2`, scheme `exact`, USDC atomic amounts (6 decimals): metadata `/v1/metadata/*` = `10000` ($0.01), metrics `/v1/metrics/*` = `50000` ($0.05). Mainnet network `eip155:8453` (`https://x402.glassnode.com`), testnet `eip155:84532` / Base Sepolia (`a testnet x402 endpoint`). +- **Bulk is unsupported over x402** (`/bulk` 404s); no special handling — document only. +- Every commit runs the Husky pre-commit hook (eslint + prettier + vitest related). Keep lint/format clean. +- Follow existing code style (2-space, single quotes, semicolons; Prettier-enforced). + +--- + +## File structure + +| File | Responsibility | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `src/types/config.ts` (modify) | Add `x402` flag + URL constants; make `apiUrl`/`apiKey` optional; add cross-field refines. | +| `src/glassnode-api.ts` (modify) | Resolve base URL from `x402`; omit `api_key` when no key. | +| `src/errors.ts` (modify) | Friendly `402` message. | +| `src/x402.ts` (new) | `createX402Fetch()` + pure helpers (`usdcDecimalToAtomic`, `createMaxAmountPolicy`). Only file that touches crypto. | +| `package.json` (modify) | `./x402` subpath export; optional peer + dev deps; version bump. | +| `tsconfig.browser.json` (modify) | Exclude `src/x402.ts` from the browser type program. | +| `test/x402.spec.ts` (new) | Unit tests for the helper (pure fns + real-deps smoke). | +| `test/x402.missing-deps.spec.ts` (new) | Missing-optional-deps error path (mocked import). | +| `test/x402.integration.spec.ts` (new) | Opt-in testnet integration (env-gated, self-skips). | +| `README.md` (modify) | "Paid calls with x402" section. | +| `CHANGELOG.md` (modify) | 0.8.0 entry. | + +Task order respects dependencies: config → client wiring → error → packaging/deps (so optional deps are installed) → helper → integration test → docs. + +--- + +### Task 1: Config schema + client base-URL resolution + +**Files:** + +- Modify: `src/types/config.ts` +- Modify: `src/glassnode-api.ts` (imports; `apiKey` field type; constructor; `request()` query) +- Test: `test/config.spec.ts` (new), `test/glassnode-api.spec.ts` (append) + +> **Done as one task/commit on purpose.** Dropping the Zod `apiUrl` default without also moving base-URL resolution into the constructor breaks the existing `should create an instance with default API URL` test (`test/glassnode-api.spec.ts`), and the Husky pre-commit hook (`vitest related`) would then block a partial commit. Config + constructor land together so the tree is green at the commit boundary. + +**Interfaces:** + +- Produces: `DEFAULT_API_URL`, `X402_API_URL`, `X402_TESTNET_API_URL` (string consts); `GlassnodeConfigSchema` (now with `x402: boolean`, optional `apiKey`/`apiUrl`, two refines); `GlassnodeConfig` type (name unchanged); `GlassnodeAPI` whose base URL is `apiUrl ?? (x402 ? X402_API_URL : DEFAULT_API_URL)` and which omits `api_key` when no key is set. + +- [ ] **Step 1: Write the failing config test** + +Create `test/config.spec.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { + GlassnodeConfigSchema, + DEFAULT_API_URL, + X402_API_URL, + X402_TESTNET_API_URL, +} from '../src/types/config'; + +describe('GlassnodeConfigSchema', () => { + it('exposes the URL constants', () => { + expect(DEFAULT_API_URL).toBe('https://api.glassnode.com'); + expect(X402_API_URL).toBe('https://x402.glassnode.com'); + expect(X402_TESTNET_API_URL).toBe('a testnet x402 endpoint'); + }); + + it('requires apiKey when x402 is not enabled', () => { + expect(() => GlassnodeConfigSchema.parse({})).toThrow(/apiKey/); + expect(GlassnodeConfigSchema.parse({ apiKey: 'k' }).apiKey).toBe('k'); + }); + + it('allows omitting apiKey when x402 is enabled, but then requires fetch', () => { + const fetchFn = (async () => new Response()) as unknown as typeof fetch; + expect(() => GlassnodeConfigSchema.parse({ x402: true })).toThrow(/fetch/); + const parsed = GlassnodeConfigSchema.parse({ x402: true, fetch: fetchFn }); + expect(parsed.x402).toBe(true); + expect(parsed.apiKey).toBeUndefined(); + }); + + it('defaults x402 to false and apiUrl to undefined', () => { + const parsed = GlassnodeConfigSchema.parse({ apiKey: 'k' }); + expect(parsed.x402).toBe(false); + expect(parsed.apiUrl).toBeUndefined(); + }); +}); +``` + +- [ ] **Step 2: Write the failing x402-mode client tests** + +Append to `test/glassnode-api.spec.ts`, inside the top-level `describe('GlassnodeAPI', ...)`: + +```ts +describe('x402 mode', () => { + const okFetch = () => + vi.fn().mockResolvedValue({ + ok: true, + json: vi.fn().mockResolvedValue(mockMetricListResponse), + }); + + it('routes to the x402 host when x402 is true', async () => { + const fetchFn = okFetch(); + const api = new GlassnodeAPI({ x402: true, fetch: fetchFn }); + await api.getMetricList(); + expect(fetchFn).toHaveBeenCalledWith( + expect.stringContaining('https://x402.glassnode.com/v1/metadata/metrics') + ); + }); + + it('omits api_key when no apiKey is set', async () => { + const fetchFn = okFetch(); + const api = new GlassnodeAPI({ x402: true, fetch: fetchFn }); + await api.getMetricList(); + const calledUrl = fetchFn.mock.calls[0][0] as string; + expect(calledUrl).not.toContain('api_key'); + }); + + it('an explicit apiUrl overrides the x402 preset', async () => { + const fetchFn = okFetch(); + const api = new GlassnodeAPI({ + x402: true, + apiUrl: 'a testnet x402 endpoint', + fetch: fetchFn, + }); + await api.getMetricList(); + expect(fetchFn).toHaveBeenCalledWith( + expect.stringContaining('a testnet x402 endpoint/v1/metadata/metrics') + ); + }); +}); +``` + +- [ ] **Step 3: Run both to verify they fail** + +Run: `pnpm exec vitest run test/config.spec.ts test/glassnode-api.spec.ts` +Expected: FAIL — `config.spec.ts` can't import the new constants; the x402-mode tests still hit `https://api.glassnode.com` and append `api_key`. + +- [ ] **Step 4: Implement the config schema** + +Replace the contents of `src/types/config.ts` with: + +```ts +import { z } from 'zod'; + +/** + * Logger function type for API call logging + */ +export type Logger = (message: string, ...args: unknown[]) => void; + +/** + * Fetch function type matching the standard fetch API + */ +export type FetchFn = typeof fetch; + +/** Default free Glassnode API base URL. */ +export const DEFAULT_API_URL = 'https://api.glassnode.com'; +/** x402 (paid) Glassnode API base URL — Base mainnet. */ +export const X402_API_URL = 'https://x402.glassnode.com'; +/** x402 testnet base URL — Base Sepolia. */ +export const X402_TESTNET_API_URL = 'a testnet x402 endpoint'; + +/** + * Zod schema for Glassnode API configuration + */ +export const GlassnodeConfigSchema = z + .object({ + /** API key for authentication. Required unless `x402` is enabled. */ + apiKey: z.string().min(1, 'API key is required').optional(), + + /** Base URL for the Glassnode API. An explicit value always wins over the `x402` preset. */ + apiUrl: z.string().url().optional(), + + /** Route requests through the x402 paid endpoint (`https://x402.glassnode.com`). */ + x402: z.boolean().default(false), + + /** Optional logger for API call debugging. */ + logger: z.function().optional(), + + /** Optional custom fetch function (e.g. an x402-wrapped fetch, or for testing). */ + fetch: z.function().optional(), + + /** Maximum number of retries for retryable errors (429 and 5xx). */ + maxRetries: z.number().int().nonnegative().default(0), + + /** Base delay in milliseconds between retries (doubles each attempt). */ + retryDelay: z.number().int().positive().default(1000), + }) + .refine((c) => c.x402 || (c.apiKey !== undefined && c.apiKey.length > 0), { + message: 'apiKey is required unless x402 is enabled', + path: ['apiKey'], + }) + .refine((c) => !c.x402 || c.fetch !== undefined, { + message: + 'fetch is required when x402 is enabled — pass an x402-capable fetch (see glassnode-api/x402)', + path: ['fetch'], + }); + +/** + * Configuration for the Glassnode API client + */ +export type GlassnodeConfig = z.input; +``` + +- [ ] **Step 5: Implement the client changes** + +In `src/glassnode-api.ts`: + +5a. Update the import at the top (add the two constants): + +```ts +import { + GlassnodeConfig, + GlassnodeConfigSchema, + Logger, + FetchFn, + DEFAULT_API_URL, + X402_API_URL, +} from './types/config'; +``` + +5b. Change the field declaration (was `private apiKey: string;`): + +```ts + private apiKey: string | undefined; +``` + +5c. In the constructor, replace the `this.apiKey` / `this.apiUrl` assignments: + +```ts +this.apiKey = validatedConfig.apiKey; +this.apiUrl = validatedConfig.apiUrl ?? (validatedConfig.x402 ? X402_API_URL : DEFAULT_API_URL); +``` + +5d. In `request()`, replace the `queryParams` construction (was `new URLSearchParams({ ...params, api_key: this.apiKey })`): + +```ts +const queryParams = new URLSearchParams({ + ...params, + ...(this.apiKey ? { api_key: this.apiKey } : {}), +}); +``` + +- [ ] **Step 6: Run the full suite to verify green** + +Run: `pnpm exec vitest run` +Expected: PASS — the existing 26 tests (including `should create an instance with default API URL`), the 4 config tests, and the 3 x402-mode tests. + +- [ ] **Step 7: Commit** + +```bash +git add src/types/config.ts src/glassnode-api.ts test/config.spec.ts test/glassnode-api.spec.ts +git commit -m "feat(config): x402 preset, base-url resolution, conditional apiKey/fetch" +``` + +--- + +### Task 2: Friendly 402 error message + +**Files:** + +- Modify: `src/errors.ts:1-7` +- Test: `test/glassnode-api.spec.ts` (append) + +**Interfaces:** + +- Produces: `GlassnodeApiError` with a helpful `402` message; `isRetryable` stays `false` for `402`. + +- [ ] **Step 1: Write the failing tests** + +Append inside `describe('error handling', ...)` in `test/glassnode-api.spec.ts`: + +```ts +it('gives a helpful 402 message and marks it non-retryable', () => { + const err = new GlassnodeApiError(402, 'Payment Required'); + expect(err.message).toContain('Payment required'); + expect(err.message).toContain('glassnode-api/x402'); + expect(err.isRetryable).toBe(false); +}); +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: `pnpm exec vitest run test/glassnode-api.spec.ts -t "402 message"` +Expected: FAIL — message is the generic status text, not the friendly copy. + +- [ ] **Step 3: Implement** + +In `src/errors.ts`, add the `402` entry to `STATUS_MESSAGES`: + +```ts +const STATUS_MESSAGES: Record = { + 400: 'Bad request', + 401: 'Invalid or missing API key', + 402: 'Payment required — pass an x402-capable fetch (see glassnode-api/x402)', + 403: 'Access forbidden — check your API tier', + 404: 'Endpoint or metric not found', + 429: 'Rate limit exceeded', +}; +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `pnpm exec vitest run test/glassnode-api.spec.ts -t "402 message"` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add src/errors.ts test/glassnode-api.spec.ts +git commit -m "feat(errors): friendly 402 payment-required message" +``` + +--- + +### Task 3: Packaging — optional deps, subpath export, browser exclude + +**Files:** + +- Modify: `package.json` (exports, peerDependencies, peerDependenciesMeta, devDependencies) +- Modify: `tsconfig.browser.json` (exclude) + +**Interfaces:** + +- Produces: the `@x402/fetch`, `@x402/evm`, `viem` module specifiers resolvable at build/test time; the `glassnode-api/x402` subpath mapped to `dist/x402.js`. Task 4 depends on this. + +- [ ] **Step 1: Add the optional deps as devDependencies (installs them)** + +Run: + +```bash +pnpm add -D --config.minimumReleaseAge=0 @x402/fetch@^2.18.0 @x402/evm@^2.18.0 viem@^2.48.11 +``` + +Expected: `@x402/fetch`, `@x402/evm`, `viem` added under `devDependencies`; `pnpm-lock.yaml` updated. + +- [ ] **Step 2: Declare them as optional peer dependencies** + +Edit `package.json` — add these two top-level keys (place after `"dependencies"`): + +```json + "peerDependencies": { + "@x402/fetch": ">=2.18.0", + "@x402/evm": ">=2.18.0", + "viem": "^2.48.11" + }, + "peerDependenciesMeta": { + "@x402/fetch": { "optional": true }, + "@x402/evm": { "optional": true }, + "viem": { "optional": true } + }, +``` + +- [ ] **Step 3: Add the subpath export** + +In `package.json`, change the `"exports"` block to add the `./x402` entry: + +```json + "exports": { + ".": { + "import": "./dist/glassnode-api.esm.min.js", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./x402": { + "types": "./dist/x402.d.ts", + "import": "./dist/x402.js", + "require": "./dist/x402.js" + } + }, +``` + +- [ ] **Step 4: Exclude the crypto file from the browser type program** + +In `tsconfig.browser.json`, add `"src/x402.ts"` to `exclude`: + +```json + "exclude": ["node_modules", "dist", "test", "examples", "src/x402.ts"] +``` + +- [ ] **Step 5: Verify install + existing build/tests still pass** + +Run: `pnpm install --config.minimumReleaseAge=0 && pnpm run build && pnpm run build:browser && pnpm test` +Expected: all succeed; no `dist/x402.*` yet (created in Task 5), browser bundle unchanged. + +> **Fallback:** if a later `pnpm run build:browser` (Task 4 Step 7) still tries to type-check `src/x402.ts` and errors on `@x402/*`/`viem` resolution, also pass an explicit exclude to the Rollup TypeScript plugin in `rollup.config.mjs`: change `typescript({ tsconfig: './tsconfig.browser.json' })` to `typescript({ tsconfig: './tsconfig.browser.json', exclude: ['src/x402.ts'] })`. + +- [ ] **Step 6: Commit** + +```bash +git add package.json pnpm-lock.yaml tsconfig.browser.json +git commit -m "chore(x402): optional peer deps, ./x402 subpath export, browser exclude" +``` + +--- + +### Task 4: `createX402Fetch` helper + +**Files:** + +- Create: `src/x402.ts` +- Test: `test/x402.spec.ts`, `test/x402.missing-deps.spec.ts` + +**Interfaces:** + +- Consumes: `@x402/fetch` (`wrapFetchWithPayment`, `x402Client`), `@x402/evm` (`ExactEvmScheme`), `viem` (`LocalAccount` type only). +- Produces: + - `usdcDecimalToAtomic(value: string): bigint` + - `createMaxAmountPolicy(maxAtomic: bigint): (x402Version: number, requirements: { amount: string }[]) => { amount: string }[]` + - `createX402Fetch(options: { account: LocalAccount; maxPaymentPerCall?: string; fetch?: typeof fetch }): Promise` + +- [ ] **Step 1: Write the failing unit tests (pure fns + smoke)** + +Create `test/x402.spec.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { usdcDecimalToAtomic, createMaxAmountPolicy, createX402Fetch } from '../src/x402'; + +describe('usdcDecimalToAtomic', () => { + it('converts USDC decimals to 6-decimal atomic units', () => { + expect(usdcDecimalToAtomic('0.06')).toBe(60000n); + expect(usdcDecimalToAtomic('0.05')).toBe(50000n); + expect(usdcDecimalToAtomic('0.01')).toBe(10000n); + expect(usdcDecimalToAtomic('1')).toBe(1000000n); + expect(usdcDecimalToAtomic('0')).toBe(0n); + }); + + it('truncates beyond 6 decimals and rejects bad input', () => { + expect(usdcDecimalToAtomic('0.1234567')).toBe(123456n); + expect(() => usdcDecimalToAtomic('abc')).toThrow(/Invalid USDC amount/); + expect(() => usdcDecimalToAtomic('-1')).toThrow(/Invalid USDC amount/); + }); +}); + +describe('createMaxAmountPolicy', () => { + it('keeps only requirements at or below the cap', () => { + const policy = createMaxAmountPolicy(60000n); + const reqs = [{ amount: '50000' }, { amount: '60000' }, { amount: '70000' }]; + expect(policy(2, reqs)).toEqual([{ amount: '50000' }, { amount: '60000' }]); + }); +}); + +describe('createX402Fetch', () => { + it('returns a callable fetch using the real x402 client', async () => { + const account = { + address: '0x0000000000000000000000000000000000000001' as const, + signTypedData: async () => '0x' as const, + }; + const wrapped = await createX402Fetch({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + account: account as any, + maxPaymentPerCall: '0.06', + }); + expect(typeof wrapped).toBe('function'); + }); +}); +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `pnpm exec vitest run test/x402.spec.ts` +Expected: FAIL — `../src/x402` does not exist. + +- [ ] **Step 3: Implement `src/x402.ts`** + +Create `src/x402.ts` (this exact source is type-checked against the installed `@x402/*` and `viem` types under TS 6.0.3): + +```ts +import type { LocalAccount } from 'viem'; + +/** Options for {@link createX402Fetch}. */ +export interface X402FetchOptions { + /** viem account used to sign payment authorizations (e.g. `privateKeyToAccount(pk)`). */ + account: LocalAccount; + /** Per-call spend ceiling in USDC (decimal string). Default `'0.06'` (just above the $0.05 metric price). */ + maxPaymentPerCall?: string; + /** Base fetch to wrap. Default `globalThis.fetch`. */ + fetch?: typeof fetch; +} + +const DEFAULT_MAX_PAYMENT_PER_CALL = '0.06'; +const USDC_DECIMALS = 6; +// Base mainnet + Base Sepolia (CAIP-2). Registering both lets one wrapped fetch serve either host. +const X402_NETWORKS = ['eip155:8453', 'eip155:84532'] as const; + +/** Convert a USDC decimal string (e.g. `'0.06'`) to atomic units (6 decimals). Truncates extra decimals. */ +export function usdcDecimalToAtomic(value: string): bigint { + if (!/^\d+(\.\d+)?$/.test(value)) { + throw new Error(`Invalid USDC amount: "${value}"`); + } + const [whole, frac = ''] = value.split('.'); + const fracPadded = (frac + '0'.repeat(USDC_DECIMALS)).slice(0, USDC_DECIMALS); + return BigInt(whole) * 10n ** BigInt(USDC_DECIMALS) + BigInt(fracPadded || '0'); +} + +/** Build a payment policy that rejects any payment requirement above `maxAtomic` (atomic USDC units). */ +export function createMaxAmountPolicy(maxAtomic: bigint) { + return (_x402Version: number, requirements: { amount: string }[]): { amount: string }[] => + requirements.filter((r) => BigInt(r.amount) <= maxAtomic); +} + +/** + * Create an x402-capable `fetch` for paid Glassnode calls (Node-first). + * + * Dynamically loads the optional peer deps `@x402/fetch` + `@x402/evm`; pass the result as the + * `fetch` option of `GlassnodeAPI` together with `x402: true`. + */ +export async function createX402Fetch(options: X402FetchOptions): Promise { + const { + account, + maxPaymentPerCall = DEFAULT_MAX_PAYMENT_PER_CALL, + fetch: baseFetch = globalThis.fetch, + } = options; + + const maxAtomic = usdcDecimalToAtomic(maxPaymentPerCall); + + const [x402fetchMod, evmMod] = await Promise.all([ + import('@x402/fetch'), + import('@x402/evm'), + ]).catch((err) => { + throw new Error( + "createX402Fetch requires the optional peer dependencies '@x402/fetch', '@x402/evm', and 'viem'. Install them: pnpm add @x402/fetch @x402/evm viem", + { cause: err } + ); + }); + const { wrapFetchWithPayment, x402Client } = x402fetchMod; + const { ExactEvmScheme } = evmMod; + + let client = new x402Client(); + for (const network of X402_NETWORKS) { + client = client.register( + network, + new ExactEvmScheme(account as ConstructorParameters[0]) + ); + } + client = client.registerPolicy( + createMaxAmountPolicy(maxAtomic) as unknown as Parameters[0] + ); + + return wrapFetchWithPayment(baseFetch, client) as typeof fetch; +} +``` + +- [ ] **Step 4: Run the unit tests to verify they pass** + +Run: `pnpm exec vitest run test/x402.spec.ts` +Expected: PASS. + +- [ ] **Step 5: Write the missing-deps test** + +Create `test/x402.missing-deps.spec.ts` (module-level mock makes `@x402/evm` fail to import): + +```ts +import { describe, it, expect, vi } from 'vitest'; + +// Simulate the optional peer dep being absent: importing it throws. +vi.mock('@x402/evm', () => { + throw new Error('Cannot find package @x402/evm'); +}); + +describe('createX402Fetch without optional deps', () => { + it('throws a clear install error', async () => { + const { createX402Fetch } = await import('../src/x402'); + const account = { + address: '0x0000000000000000000000000000000000000001' as const, + signTypedData: async () => '0x' as const, + }; + await expect( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createX402Fetch({ account: account as any }) + ).rejects.toThrow(/optional peer dependencies/); + }); +}); +``` + +- [ ] **Step 6: Run the missing-deps test** + +Run: `pnpm exec vitest run test/x402.missing-deps.spec.ts` +Expected: PASS — the rejection message contains "optional peer dependencies". + +- [ ] **Step 7: Verify the whole build + full suite + browser build** + +Run: `pnpm run lint && pnpm run build && pnpm run build:browser && pnpm test` +Expected: all pass. `dist/x402.js` + `dist/x402.d.ts` produced by `tsc`; the browser bundle is unchanged and does NOT include x402 (excluded). + +- [ ] **Step 8: Commit** + +```bash +git add src/x402.ts test/x402.spec.ts test/x402.missing-deps.spec.ts +git commit -m "feat(x402): createX402Fetch helper (subpath glassnode-api/x402)" +``` + +--- + +### Task 5: Opt-in testnet integration test + +**Files:** + +- Create: `test/x402.integration.spec.ts` +- Modify: `package.json` (add `test:x402` script) + +**Interfaces:** + +- Consumes: `createX402Fetch`, `GlassnodeAPI`, `X402_TESTNET_API_URL`, `viem/accounts.privateKeyToAccount`. +- Produces: an env-gated E2E test that self-skips without `X402_TESTNET_PRIVATE_KEY` (so `pnpm test`/CI stay hermetic). + +- [ ] **Step 1: Write the integration test** + +Create `test/x402.integration.spec.ts`: + +```ts +import { describe, it, expect } from 'vitest'; +import { privateKeyToAccount } from 'viem/accounts'; +import { GlassnodeAPI } from '../src/glassnode-api'; +import { X402_TESTNET_API_URL } from '../src/types/config'; +import { createX402Fetch } from '../src/x402'; + +const KEY = process.env.X402_TESTNET_PRIVATE_KEY; + +// Requires a Base-Sepolia wallet funded with test USDC. Skipped unless the key is provided. +describe.skipIf(!KEY)('x402 testnet integration', () => { + it('pays for a metric on the testnet endpoint and returns validated data', async () => { + const account = privateKeyToAccount(KEY as `0x${string}`); + const api = new GlassnodeAPI({ + x402: true, + apiUrl: X402_TESTNET_API_URL, + fetch: await createX402Fetch({ account, maxPaymentPerCall: '0.06' }), + }); + + const data = await api.callMetric<{ t: number; v: number }[]>('/market/mvrv', { + a: 'BTC', + i: '24h', + }); + + expect(Array.isArray(data)).toBe(true); + expect(data.length).toBeGreaterThan(0); + expect(typeof data[0].t).toBe('number'); + expect(typeof data[0].v).toBe('number'); + }, 60_000); +}); +``` + +- [ ] **Step 2: Add the `test:x402` script** + +In `package.json` `"scripts"`, add: + +```json + "test:x402": "vitest run test/x402.integration.spec.ts", +``` + +- [ ] **Step 3: Verify it skips cleanly without the env var** + +Run: `pnpm exec vitest run test/x402.integration.spec.ts` +Expected: PASS with the suite **skipped** (0 failures; the describe is skipped because `X402_TESTNET_PRIVATE_KEY` is unset). + +- [ ] **Step 4: Commit** + +```bash +git add test/x402.integration.spec.ts package.json +git commit -m "test(x402): opt-in testnet integration test + test:x402 script" +``` + +--- + +### Task 6: Docs + version bump + +**Files:** + +- Modify: `README.md`, `CHANGELOG.md`, `package.json` (version) + +**Interfaces:** + +- Produces: user-facing docs for the feature; `0.8.0` release entry. + +- [ ] **Step 1: Add the README section** + +In `README.md`, add a `## Paid calls with x402` entry to the Table of Contents (after `[Bulk Metrics](#bulk-metrics)`), and insert this section immediately before `## Browser`: + +````markdown +## Paid calls with x402 + +Glassnode also serves a **paid, per-call API over the [x402 protocol](https://x402.org)** at +`https://x402.glassnode.com` — no API key required, you pay per request in USDC on Base +($0.01/metadata call, $0.05/metric call). This is **Node-first** and opt-in: the crypto stack +(`@x402/fetch`, `@x402/evm`, `viem`) is an **optional peer dependency**, installed only if you use it. + +```bash +pnpm add glassnode-api @x402/fetch @x402/evm viem +``` + +```typescript +import { GlassnodeAPI } from 'glassnode-api'; +import { createX402Fetch } from 'glassnode-api/x402'; +import { privateKeyToAccount } from 'viem/accounts'; + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); + +const api = new GlassnodeAPI({ + x402: true, // → https://x402.glassnode.com + fetch: await createX402Fetch({ + account, + maxPaymentPerCall: '0.06', // USDC per-call ceiling (default) + }), +}); + +// Pays $0.05 USDC on Base, transparently: +const mvrv = await api.callMetric('/market/mvrv', { a: 'BTC', i: '24h' }); +``` + +**`createX402Fetch(options)`** + +| Option | Type | Default | Description | +| ------------------- | -------------- | ------------------ | -------------------------------- | +| `account` | `LocalAccount` | — (**required**) | viem account that signs payments | +| `maxPaymentPerCall` | `string` | `'0.06'` | Per-call USDC spend ceiling | +| `fetch` | `typeof fetch` | `globalThis.fetch` | Base fetch to wrap | + +> **Spend safety:** `maxPaymentPerCall` caps a **single** request — it is **not** a cumulative budget, so +> an agent loop can still spend within that ceiling repeatedly. Use a **dedicated, funded-but-limited** +> wallet (never your primary key), and load the key from the environment — never hardcode it. + +**Notes** + +- **Bulk metrics are not available over x402** — `callBulkMetric()` only works against the free + `api.glassnode.com`. +- **Testnet:** target Base Sepolia by passing `apiUrl: 'a testnet x402 endpoint'`. +- **Browser** signing is not supported yet (planned). +```` + +- [ ] **Step 2: Add the CHANGELOG entry** + +In `CHANGELOG.md`, add at the top (below `# Changelog`): + +```markdown +## 0.8.0 + +- Add opt-in, Node-first **x402 payment support**: `x402: true` config preset (routes to + `https://x402.glassnode.com`) and a new `glassnode-api/x402` subpath export with + `createX402Fetch({ account, maxPaymentPerCall })`. The crypto stack (`@x402/fetch`, `@x402/evm`, + `viem`) is an optional peer dependency; the core package stays `zod`-only. +- `apiKey` is now optional when `x402` is enabled; `fetch` is required in that mode. +- Add a friendly `402` error message. Bulk metrics remain free-API only (unsupported over x402). +``` + +- [ ] **Step 3: Bump the version to 0.8.0** + +Edit `package.json`: change `"version"` to `"0.8.0"`. + +- [ ] **Step 4: Verify the full pipeline once more** + +Run: `pnpm run lint && pnpm test && pnpm run build && pnpm run build:browser` +Expected: all pass. + +- [ ] **Step 5: Commit** + +```bash +git add README.md CHANGELOG.md package.json +git commit -m "docs(x402): README section, CHANGELOG, bump to 0.8.0" +``` + +--- + +## Self-review notes (for the implementer) + +- **Spec coverage:** config preset + URL resolution + api_key omission (T1), 402 error (T2), packaging/optional-deps/browser-exclude (T3), helper + spend cap + missing-deps error (T4), testnet integration (T5), docs/bulk-limitation/version (T6). All spec sections map to a task. +- **Type names are consistent across tasks:** `createX402Fetch`, `usdcDecimalToAtomic`, `createMaxAmountPolicy`, `X402_API_URL`, `X402_TESTNET_API_URL`, `DEFAULT_API_URL`. +- **Verified before writing:** the `src/x402.ts` source in Task 5 was type-checked against the real installed `@x402/fetch@2.18`, `@x402/evm@2.18`, and `viem` `.d.ts` under `tsc 6.0.3` (clean), and the client chain (`new x402Client().register(...).registerPolicy(...)` → `wrapFetchWithPayment`) was smoke-run in Node. +- **If `pnpm add` is blocked by the local `.npmrc` release-age quarantine**, the `--config.minimumReleaseAge=0` flag (already in the commands) overrides it for that install. diff --git a/docs/superpowers/specs/2026-07-14-x402-support-design.md b/docs/superpowers/specs/2026-07-14-x402-support-design.md new file mode 100644 index 0000000..c5266a8 --- /dev/null +++ b/docs/superpowers/specs/2026-07-14-x402-support-design.md @@ -0,0 +1,268 @@ +# Design: x402 payment support (Node-first) + +**Date:** 2026-07-14 +**Status:** Approved design — ready for implementation planning +**Package:** `glassnode-api` + +## Overview + +Glassnode now exposes a paid, per-call API over the [x402 payment protocol](https://x402.org) +at `https://x402.glassnode.com` (mainnet) and `a testnet x402 endpoint` (testnet). Requests that +require payment return `402 Payment Required` with a header-based challenge; an x402-aware client signs a +USDC payment authorization and retries. + +**Verified from the live `402` challenges** (`payment-required` header, base64 JSON, `x402Version: 2`, +scheme `exact`; `accepts[].amount` is USDC atomic units, 6 decimals — always authoritative): + +| Endpoint class | Price | Mainnet network / asset | Testnet network / asset | +| --------------------------- | ------------------- | ----------------------------------------- | -------------------------------------------- | +| Metadata (`/v1/metadata/*`) | `10000` = **$0.01** | `eip155:8453` (Base) / USDC `0x8335…2913` | `eip155:84532` (Base Sepolia) / `0x036C…F7e` | +| Metrics (`/v1/metrics/*`) | `50000` = **$0.05** | same | same | + +**Bulk metrics are NOT exposed over x402** — `GET /v1/metrics/.../bulk` returns `404` on the x402 host. +`callBulkMetric()` is therefore unsupported in x402 mode (see Non-goals). + +This design adds first-class x402 support to the library **without** shipping crypto/wallet code in the +core package. x402 tooling is opt-in via a subpath export and optional peer dependencies. + +## Goals + +- Let a Node.js caller make paid Glassnode calls through the existing `GlassnodeAPI` client. +- Keep the core package's footprint unchanged: single runtime dependency (`zod`), browser-friendly. +- Make the crypto stack (`@x402/fetch`, `@x402/evm`, `viem`) **optional** — installed only by users who make paid calls. +- Provide a built-in spend guard with a safe default. + +## Non-goals (YAGNI) + +- Browser signing (injected wallet / EIP-1193). Explicitly deferred to a later iteration; the design + leaves room for it in the same subpath. +- **Bulk metrics over x402.** The x402 host 404s on `/v1/metrics/.../bulk`; `callBulkMetric()` stays a + free-API (`api.glassnode.com`) feature only. In x402 mode a bulk call will surface the normal `404` + error; the README documents this limitation. (No special guard in v1 unless we later choose to throw a + clearer message.) +- Networks other than Base / Base Sepolia, or tokens other than USDC. +- Payment receipts, balance top-ups, streaming, or any wallet management beyond signing a call. + +## Key decisions + +1. **Approach A — subpath export + core preset.** The turnkey helper lives at the `glassnode-api/x402` + subpath; the core entry (`glassnode-api`) never imports crypto. Chosen over an all-in-core config + (leaks crypto into the core module graph; constructors can't `await` a dynamic import) and over a + separate companion package (extra release pipeline for a small helper). +2. **x402 client: `@x402/fetch` v2 + `@x402/evm` (Coinbase CDP-recommended).** The live Glassnode endpoints + return **`x402Version: 2`** (header-based `payment-required` challenge, CAIP-2 networks `eip155:8453`). + The **Coinbase CDP buyer quickstart itself now installs `@x402/fetch @x402/evm`** — the scoped v2 + packages are Coinbase's current recommendation; the unscoped `x402-fetch` v1 (x402Version 1, network + `"base"`) is the deprecated predecessor and cannot parse a v2 challenge. So "the Coinbase client" and + "the foundation v2 client" are the same thing (`@x402/fetch` v2). v2 setup requires registering the + exact-EVM scheme and building a client: + ```ts + import { wrapFetchWithPayment, x402Client } from '@x402/fetch'; + import { registerExactEvmScheme } from '@x402/evm/exact/client'; + ``` + Exact `x402Client` construction and the max-amount mechanism are confirmed against the installed + `@x402/fetch`/`@x402/evm` `.d.ts` during implementation (npm readmes are empty). +3. **Spend safety exposed with a default cap.** `maxPaymentPerCall` (USDC decimal string) defaults to + `'0.06'` (just above the $0.05 metrics price), converted to atomic units (`parseUnits(value, 6)`) and + passed to the client's max-amount guard. +4. **Node-first.** Browser parity is a separate, later effort. + +## Architecture + +### Module layout + +| File | Change | Notes | +| ---------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `src/x402.ts` | **new** | Compiled to subpath export `glassnode-api/x402`. Exports `createX402Fetch` + types. **All** optional-dep usage is behind a dynamic `import()` here. | +| `src/types/config.ts` | edit | Add `x402?: boolean`; export `X402_API_URL = 'https://x402.glassnode.com'`, `X402_TESTNET_API_URL = 'a testnet x402 endpoint'`, **and** a source-level `DEFAULT_API_URL = 'https://api.glassnode.com'` constant (today that literal only lives inside the Zod default). Change `apiUrl` to `z.string().url().optional()` (keep `.url()`, drop `.default()`) so "explicitly set" is detectable. Make `apiKey` `.optional()` and add object-level `.refine`s: (a) `apiKey` required when `x402` is falsy; (b) **`fetch` required when `x402` is `true`** (a plain fetch can't pay). | +| `src/glassnode-api.ts` | edit | Base-URL resolution (see below); change the `private apiKey` field type to `string \| undefined` and the `this.apiKey =` assignment accordingly. Request path, retries, and Zod validation otherwise unchanged. | +| `src/errors.ts` | edit | Add a friendly `402` message. | +| `src/index.ts` | unchanged | Deliberately does **not** re-export the helper, keeping the core entry `zod`-only. `X402_API_URL` and the `x402` config flag flow through the normal config exports (plain strings/booleans, no crypto). | + +### Core API + +```ts +new GlassnodeAPI({ + apiKey?: string, + x402?: boolean, // default false + apiUrl?: string, // explicit override always wins + fetch?: typeof fetch, // pass an x402-wrapped fetch for paid calls + // ...existing options unchanged +}); +``` + +Base-URL resolution (in the constructor): + +``` +apiUrl ?? (x402 ? X402_API_URL : DEFAULT_API_URL) +``` + +- Fully backward-compatible: with neither `apiUrl` nor `x402`, the URL stays `https://api.glassnode.com`. +- **`fetch` is required when `x402: true`.** Enforced at construction via a Zod refine — without a + payment-capable fetch every call would just `402`. The error message points to `glassnode-api/x402`. +- **Testnet:** `x402: true` defaults to mainnet; target Base Sepolia by passing + `apiUrl: X402_TESTNET_API_URL` explicitly (an explicit `apiUrl` always wins over the preset). +- **`apiKey` becomes optional under x402.** Glassnode's x402 endpoint is authorized by payment, not an API + key (its own curl example sends no `api_key`). So: + - Config validation: `apiKey` is required when `x402` is falsy (unchanged, backward-compatible) and + **optional** when `x402` is `true`. Implement with a Zod `superRefine`/`refine` on the config object. + Note this is a **runtime-only** guard: `GlassnodeConfig = z.input<...>` will type `apiKey` as optional + unconditionally (cross-field requiredness isn't expressible in the input type), so omitting `apiKey` + with `x402` falsy compiles but throws at construction. (Verified: a Zod-4 object `.refine` still parses + correctly and nothing in the repo relies on `.shape`/`.extend` of this schema.) + - Request building: `request()` appends `api_key=` **only when an `apiKey` is present**. When absent + (x402-only usage) the param is omitted entirely. An API key _may_ still be supplied alongside x402 if + the caller has one; it is not forced. +- No other changes to the request loop: the injected x402-wrapped `fetch` handles `402` transparently, + below the library's existing `maxRetries` (429/5xx) loop and before Zod validation. + +### Helper: `glassnode-api/x402` + +```ts +import type { LocalAccount } from 'viem'; // type-only — must not trigger a runtime load + +type X402FetchOptions = { + account: LocalAccount; // viem account, e.g. privateKeyToAccount(pk) + maxPaymentPerCall?: string; // USDC decimal, default '0.06' + fetch?: typeof fetch; // base fetch to wrap, default globalThis.fetch +}; + +async function createX402Fetch(options: X402FetchOptions): Promise; +``` + +Behavior: + +1. Dynamically `import('@x402/fetch')`, `import('@x402/evm/exact/client')`, and (for signer/units) + `import('viem')`. If any is missing, throw a clear error: _"createX402Fetch requires the optional peer + dependencies `@x402/fetch`, `@x402/evm`, and `viem`. Install them: `pnpm add @x402/fetch @x402/evm +viem`."_ All value imports of the optional deps stay inside the async function; only `import type` + references appear at module scope, so importing the subpath without the peers installed does not throw + until `createX402Fetch` is actually called. +2. **Build the v2 client:** `registerExactEvmScheme(...)` then construct the `x402Client` (exact + construction confirmed against the installed `.d.ts`). x402 signs an off-chain EIP-3009 authorization + (the facilitator submits on-chain), so a bare `LocalAccount` should suffice — pass `account`; only build + a viem wallet client if the v2 client strictly requires one. +3. **Spend cap:** convert `maxPaymentPerCall` → atomic units via `parseUnits(value, 6)` (USDC, 6 decimals), + e.g. `parseUnits('0.06', 6) === 60000n`. Verify how v2 expresses the max: if `wrapFetchWithPayment`/the + client exposes a max-amount option, use it; if not, enforce it via a payment-requirements selector that + **throws when `accepts[].amount` exceeds the cap** before signing. Either way the cap must be honored. +4. Return `wrapFetchWithPayment(baseFetch, client)`. Its return type is `(input, init?) => Promise`, + which does **not** structurally include `fetch.preconnect`; cast the result `as typeof fetch` so it + satisfies the config's `FetchFn` under `strict`. + +> A `walletClient` advanced override is intentionally **cut from v1** (YAGNI) — trivially re-addable if a +> caller needs a custom transport/chain. + +### Usage (target ergonomics) + +```ts +import { GlassnodeAPI } from 'glassnode-api'; +import { createX402Fetch } from 'glassnode-api/x402'; +import { privateKeyToAccount } from 'viem/accounts'; + +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); + +const api = new GlassnodeAPI({ + x402: true, // -> https://x402.glassnode.com + fetch: await createX402Fetch({ account, maxPaymentPerCall: '0.06' }), +}); + +// Pays $0.05 USDC on Base, transparently: +const mvrv = await api.callMetric('/market/mvrv', { a: 'BTC', i: '24h' }); +``` + +## Payment & error flow + +- **Happy path:** the wrapped fetch intercepts `402`, verifies the price is within `maxValue`, signs a USDC + authorization on Base, and retries. The library only ever observes the resulting `200`, then validates + with Zod as today. +- **Composition with existing retries:** `maxRetries` handles 429/5xx and wraps the injected fetch; x402's + 402 handling lives _inside_ that fetch. No conflict. +- **Misconfiguration guard:** if a `402` reaches the library's request loop (e.g. `x402: true` but a plain, + unwrapped `fetch` was passed), map it to a friendly, **non-retryable** `GlassnodeApiError`: + _"Payment required — pass an x402-capable fetch (see `glassnode-api/x402`)."_ Add `402` to the + `STATUS_MESSAGES` map. +- **Over-cap / insufficient funds:** `wrapFetchWithPayment` throws before paying (or the payment fails); + the error propagates with the client's message through the library's existing error handling. + +## Packaging & build + +- `package.json`: + - `exports`: add a `"./x402"` subpath (`import` / `require` / `types`). + - `peerDependencies`: `@x402/fetch`, `@x402/evm`, and `viem`, all marked `optional: true` in `peerDependenciesMeta`. + - `devDependencies`: add `@x402/fetch`, `@x402/evm`, and `viem` so `src/x402.ts` type-checks and tests can run. + - `files`: existing globs (`dist/*.js`, `dist/*.d.ts`) already capture the new subpath output. + - Version: **minor** bump (new backward-compatible feature) + CHANGELOG entry. +- Node build (`tsc`): compiles `src/x402.ts` as part of `src/**/*` (NodeNext resolves viem/@x402/fetch + subpath exports fine). +- Browser build (`rollup`): **do not** add `src/x402.ts` to the inputs — the browser bundle stays + crypto-free. **Required (release-pipeline risk):** `tsconfig.browser.json` uses `include: ["src/**/*"]` + with `moduleResolution: node` (classic), which **cannot** resolve viem/@x402/fetch `exports`-map subpaths. + So `src/x402.ts` must be added to `exclude` in `tsconfig.browser.json` (and, defensively, to the + `@rollup/plugin-typescript` `exclude`), otherwise `build:browser` — and therefore `prepublishOnly` — fails + to type-check. Add the x402 test file to the same exclude. +- **Subpath is CJS-only by design.** With no `"type": "module"`, `tsc` emits `dist/x402.js` as CommonJS and + there is no rollup ESM bundle for the subpath; `import`/`require` both resolve to it. ESM consumers get it + via Node named-export interop, and its inner dynamic `import()` is the correct CJS→ESM bridge to the + ESM-only viem/@x402/fetch. No tree-shakeable ESM is expected here. + +## Testing (Vitest) + +Core (no crypto, no network): + +- `x402: true` → base URL is `https://x402.glassnode.com`. +- Explicit `apiUrl` overrides `x402`. +- Neither set → `https://api.glassnode.com` (regression guard). +- A mock x402-wrapped `fetch` flows through to validated data unchanged. +- A `402` from a plain fetch → the friendly non-retryable error. +- A `402` is **not** retried even with `maxRetries > 0` (locks in that `402` is absent from + `GlassnodeApiError.isRetryable`). +- `x402: true` with no `apiKey` constructs successfully, and the outgoing URL omits `api_key`. +- `x402: true` with an `apiKey` still appends `api_key`. +- No `x402`, no `apiKey` → constructor throws (unchanged required-key behavior). + +Helper (mock the dynamic imports; no real crypto/network): + +- Use `vi.mock('@x402/fetch', factory)` / `vi.mock('viem', factory)`. `@x402/fetch` and `viem` must be added + as **devDependencies** so the module specifiers resolve at test time; the "missing deps" case is exercised + by making the mock factory throw / reject (not by real absence). +- Missing optional deps → clear install error (via a throwing mock). +- Default `maxPaymentPerCall` converts to `60000n` (`parseUnits('0.06', 6)`) and is passed to the client's + max-amount option; assert the concrete bigint. +- Custom `maxPaymentPerCall` passed through. +- Returns a callable `fetch`. + +Testnet integration test (opt-in, real network — **not** in default CI): + +- A single end-to-end test against `a testnet x402 endpoint` (Base Sepolia, `eip155:84532`) that makes + one real paid metric call and asserts a validated `200` response. +- Gated behind an env var (e.g. `X402_TESTNET_PRIVATE_KEY`): skip when unset so unit runs and CI stay + hermetic. Requires a Base-Sepolia wallet funded with test USDC. +- Add a `test:x402` script (or a tagged Vitest project) so it runs on demand, separate from `pnpm test`. +- Purpose: prove the `@x402/fetch` v2 wiring + `createX402Fetch` actually completes a payment against a + live x402 v2 endpoint before shipping. + +## Spend safety (scope of the guard) + +`maxPaymentPerCall` bounds a **single** request only — it does **not** cap cumulative spend across many +calls, so a retry storm or an agent loop can still drain a wallet within the per-call ceiling. The library +will not model a cumulative budget in v1 (a caller can wrap their own counter). The README must: + +- State clearly that the guard is per-call, not a total budget. +- Recommend a **dedicated, funded-but-limited** wallet for agent use (not a primary key). +- Warn against hardcoding keys; the example loads `PRIVATE_KEY` from the environment. + +## Docs + +- README: a "Paid calls with x402" section (Node example, per-call spend cap + the wallet-safety warnings + above, links to Glassnode x402 + the buyer quickstart), noting browser support is planned. +- CHANGELOG entry under the new minor version. + +## References + +- x402 protocol (v2, x402-foundation): https://github.com/x402-foundation/x402 — client `@x402/fetch` + `@x402/evm` +- Coinbase CDP buyer quickstart (recommends `@x402/fetch @x402/evm`): https://docs.cdp.coinbase.com/x402/quickstart-for-buyers +- Coinbase CDP x402 welcome: https://docs.cdp.coinbase.com/x402/welcome +- Glassnode x402 skill: https://x402.glassnode.com/SKILL.md +- Live challenge shape (verified 2026-07-14): `payment-required` header, base64 JSON, `x402Version: 2`, + `accepts: [{ scheme: "exact", network: "eip155:8453" | "eip155:84532", asset: , amount: "10000" | "50000", ... }]` diff --git a/examples/.env.example b/examples/.env.example index fb701eb..0902fc5 100644 --- a/examples/.env.example +++ b/examples/.env.example @@ -1,2 +1,9 @@ # Glassnode API Key (obtain from https://docs.glassnode.com/basic-api/api-key) -GLASSNODE_API_KEY=your_api_key_here \ No newline at end of file +# Used by the free-API examples (metadata.validation.ts, metric.dump.ts, bulk.market-cap.ts). +GLASSNODE_API_KEY=your_api_key_here + +# --- x402 paid-API example (ex.x402.active-addresses.ts) --- +# No API key needed — you pay per call in USDC on Base. +X402_PRIVATE_KEY=0xyour_funded_wallet_private_key +X402_MAX_PAYMENT=0.06 # per-call USDC ceiling (metrics cost $0.05, metadata $0.01) +X402_API_URL=https://x402.glassnode.com # x402 endpoint (mainnet); point elsewhere to use another \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 72fedfb..4cefd22 100644 --- a/examples/README.md +++ b/examples/README.md @@ -48,6 +48,36 @@ Run with: npx ts-node metric.dump.ts ``` +### x402 Paid Calls — Active Addresses (`ex.x402.active-addresses.ts`) + +Demonstrates the **x402 paid API** (no API key — you pay per call in USDC on Base): + +- Build a payment-capable `fetch` from a funded Base wallet (`createX402Fetch`) +- Hit the **metadata** endpoint ($0.01) to confirm the asset + resolution are supported +- Fetch **active addresses** for the asset (default **BTC**), last 1 month at `24h` ($0.05) +- Defaults to **mainnet**; point at a different x402 endpoint by setting `X402_API_URL` + +Defaults to BTC at `24h` (`active_count` rejects `1h`). The metric/asset/resolution are overridable +via env — see the script header; the metadata check skips the paid query if the asset isn't supported. + +Set these in `.env` (see `.env.example`): + +``` +X402_PRIVATE_KEY=0xyour_funded_wallet_private_key +X402_MAX_PAYMENT=0.06 # optional, per-call USDC ceiling +X402_API_URL=https://x402.glassnode.com # x402 endpoint (mainnet); point elsewhere to use another +``` + +> **Wallet safety:** use a dedicated, funded-but-limited wallet — never a primary key. On mainnet it +> spends real USDC; on a testnet endpoint fund the wallet with Base Sepolia test USDC. `X402_MAX_PAYMENT` +> caps each call, not total spend. + +Run with: + +```bash +npx ts-node ex.x402.active-addresses.ts +``` + ## Dependencies The examples use: @@ -55,6 +85,7 @@ The examples use: - `dotenv` - For loading environment variables - `ts-node` - For running TypeScript files directly - `zod` - For schema validation (used in metadata.validation.ts) +- `@x402/fetch`, `@x402/evm`, `viem` - For the x402 paid-API example (payment signing on Base) ## Adding New Examples diff --git a/examples/ex.x402.active-addresses.ts b/examples/ex.x402.active-addresses.ts new file mode 100644 index 0000000..90a4354 --- /dev/null +++ b/examples/ex.x402.active-addresses.ts @@ -0,0 +1,116 @@ +/** + * x402 paid-API example: active addresses (configurable metric/asset/resolution). + * + * Flow: + * 1. Build a payment-capable fetch from a funded Base wallet (viem account). + * 2. Hit the METADATA endpoint ($0.01) to confirm the metric supports the asset + resolution. + * 3. Hit the METRIC endpoint ($0.05) for the last month of data at that resolution. + * + * Defaults to mainnet. Set X402_API_URL to point at a different x402 endpoint (e.g. a + * testnet); the same wallet/account works on either since payment schemes for both Base + * mainnet and Base Sepolia are registered. + * + * Notes: + * - active_count only allows 24h/1w/1month on the x402 endpoint — i=1h returns HTTP 403 + * "Resolution 1h is not allowed" (the metadata `parameters.i` list can be broader than + * what the endpoint actually serves). + * - Some assets (e.g. SUI) may only be offered on certain endpoints for a given metric — + * the metadata check reports this and skips the paid query. + * - X402_SKIP_METADATA=1 makes a single paid metric call (no metadata call first). + * + * Env (examples/.env): + * X402_API_URL optional x402 endpoint override (default: built-in mainnet) + * X402_METRIC metric path (default: /addresses/active_count) + * X402_ASSET asset symbol (default: BTC) + * X402_RESOLUTION 24h | 1w | 1month (default: 24h; 1h is not allowed) + * X402_SKIP_METADATA 1 to skip the metadata call (default: 0) + * X402_PRIVATE_KEY 0x-prefixed key of a funded Base wallet (required) + * X402_MAX_PAYMENT per-call USDC ceiling (default: 0.06) + * + * Run: npx ts-node ex.x402.active-addresses.ts + */ +import { GlassnodeAPI } from '../src'; +import { createX402Fetch } from '../src/x402'; +import { privateKeyToAccount } from 'viem/accounts'; +import 'dotenv/config'; + +const PRIVATE_KEY = process.env.X402_PRIVATE_KEY; +const API_URL = process.env.X402_API_URL; // optional endpoint override; unset → built-in mainnet +const MAX_PAYMENT = process.env.X402_MAX_PAYMENT ?? '0.06'; +const METRIC = process.env.X402_METRIC ?? '/addresses/active_count'; +const ASSET = (process.env.X402_ASSET ?? 'BTC').toUpperCase(); +const RESOLUTION = process.env.X402_RESOLUTION ?? '24h'; +const SKIP_METADATA = process.env.X402_SKIP_METADATA === '1'; +const ONE_MONTH_SECONDS = 30 * 24 * 60 * 60; + +async function main(): Promise { + if (!PRIVATE_KEY) { + throw new Error( + 'X402_PRIVATE_KEY is required — set it in examples/.env to a funded Base wallet private key (0x...).' + ); + } + + const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`); + console.log(`x402 ${API_URL ? 'endpoint override' : 'mainnet'} — wallet ${account.address}`); + console.log(`${METRIC} asset=${ASSET} i=${RESOLUTION} cap=$${MAX_PAYMENT}\n`); + + const api = new GlassnodeAPI({ + x402: true, // defaults to the built-in mainnet endpoint + ...(API_URL ? { apiUrl: API_URL } : {}), + fetch: await createX402Fetch({ account, maxPaymentPerCall: MAX_PAYMENT }), + logger: console.log, + }); + + // 1) Metadata ($0.01): confirm the metric supports the asset and resolution. + // Skip with X402_SKIP_METADATA=1 to make a single paid metric call. + if (!SKIP_METADATA) { + console.log(`\nChecking metadata for ${METRIC} ...`); + const meta = await api.getMetricMetadata(METRIC); + const assets = meta.parameters?.a ?? []; + const resolutions = meta.parameters?.i ?? []; + const hasAsset = assets.includes(ASSET); + const hasResolution = resolutions.includes(RESOLUTION); + console.log( + ` supported assets: ${assets.length} — ${ASSET} ${hasAsset ? 'present ✓' : 'MISSING ✗'}` + ); + console.log( + ` resolutions: ${resolutions.join(', ') || '(none listed)'} — ${RESOLUTION} ${hasResolution ? '✓' : 'not listed'}` + ); + + // Skip the $0.05 metric call if the metadata already says the asset is unsupported. + if (!hasAsset) { + const sample = assets.slice(0, 12).join(', '); + console.warn( + `\n⚠ ${ASSET} is not supported for ${METRIC} on this endpoint. Try one of: ${sample}${ + assets.length > 12 ? ', …' : '' + }` + ); + console.warn(' Set X402_ASSET= and re-run.'); + return; + } + } + + // 2) Metric ($0.05): last month of data at the chosen resolution. + const since = Math.floor(Date.now() / 1000) - ONE_MONTH_SECONDS; + console.log(`\nFetching ${ASSET} ${METRIC}, last 1 month @ ${RESOLUTION} ...`); + const data = await api.callMetric<{ t: number; v: number }[]>(METRIC, { + a: ASSET, + i: RESOLUTION, + s: String(since), + }); + + console.log(`\n${data.length} data points`); + if (data.length > 0) { + const first = data[0]; + const last = data[data.length - 1]; + const avg = data.reduce((sum, d) => sum + d.v, 0) / data.length; + console.log(` first: ${new Date(first.t * 1000).toISOString()} → ${first.v}`); + console.log(` last: ${new Date(last.t * 1000).toISOString()} → ${last.v}`); + console.log(` average: ${Math.round(avg)}`); + } +} + +main().catch((err) => { + console.error('\n✗ Failed:', err instanceof Error ? err.message : err); + process.exit(1); +}); diff --git a/examples/package.json b/examples/package.json index 4d2e7b4..216688e 100644 --- a/examples/package.json +++ b/examples/package.json @@ -3,7 +3,10 @@ "version": "1.0.0", "private": true, "dependencies": { - "dotenv": "^16.5.0" + "@x402/evm": "^2.18.0", + "@x402/fetch": "^2.18.0", + "dotenv": "^16.5.0", + "viem": "^2.48.11" }, "devDependencies": { "ts-node": "^10.9.2" diff --git a/package.json b/package.json index 640f453..9487d37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "glassnode-api", - "version": "0.7.7", + "version": "0.8.0", "description": "Typescript client for the Glassnode API (Node.js and Browser)", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -26,6 +26,7 @@ "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", + "test:x402": "vitest run test/x402.integration.spec.ts", "lint": "eslint .", "format": "prettier --write .", "prepare": "husky", @@ -68,6 +69,11 @@ "import": "./dist/glassnode-api.esm.min.js", "require": "./dist/index.js", "types": "./dist/index.d.ts" + }, + "./x402": { + "types": "./dist/x402.d.ts", + "import": "./dist/x402.js", + "require": "./dist/x402.js" } }, "publishConfig": { @@ -79,6 +85,16 @@ "dependencies": { "zod": "^4.4.3" }, + "peerDependencies": { + "@x402/fetch": "^2.18.0", + "@x402/evm": "^2.18.0", + "viem": "^2.48.11" + }, + "peerDependenciesMeta": { + "@x402/fetch": { "optional": true }, + "@x402/evm": { "optional": true }, + "viem": { "optional": true } + }, "devDependencies": { "@eslint/js": "^10.0.1", "@rollup/plugin-commonjs": "^29.0.3", @@ -87,6 +103,8 @@ "@rollup/plugin-typescript": "^12.3.0", "@types/node": "^26.1.1", "@vitest/coverage-v8": "^4.1.10", + "@x402/evm": "^2.18.0", + "@x402/fetch": "^2.18.0", "eslint": "^10.7.0", "husky": "^9.1.7", "lint-staged": "^17.0.8", @@ -95,6 +113,7 @@ "tslib": "^2.8.1", "typescript": "^6.0.3", "typescript-eslint": "^8.64.0", + "viem": "^2.55.2", "vitest": "^4.1.10" }, "lint-staged": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa4011e..aa7f8fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,7 @@ overrides: brace-expansion@5: '>=5.0.6' importers: + .: dependencies: zod: @@ -38,6 +39,12 @@ importers: '@vitest/coverage-v8': specifier: ^4.1.10 version: 4.1.10(vitest@4.1.10) + '@x402/evm': + specifier: ^2.18.0 + version: 2.18.0(typescript@6.0.3) + '@x402/fetch': + specifier: ^2.18.0 + version: 2.18.0 eslint: specifier: ^10.7.0 version: 10.7.0 @@ -62,129 +69,85 @@ importers: typescript-eslint: specifier: ^8.64.0 version: 8.64.0(eslint@10.7.0)(typescript@6.0.3) + viem: + specifier: ^2.55.2 + version: 2.55.2(typescript@6.0.3)(zod@4.4.3) vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.1.1)(@vitest/coverage-v8@4.1.10)(vite@8.1.4(@types/node@26.1.1)(terser@5.46.0)(yaml@2.9.0)) packages: + + '@adraffy/ens-normalize@1.11.1': + resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==} + '@babel/helper-string-parser@7.27.1': - resolution: - { - integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.29.7': - resolution: - { - integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} '@babel/helper-validator-identifier@7.28.5': - resolution: - { - integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} '@babel/helper-validator-identifier@7.29.7': - resolution: - { - integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} '@babel/parser@7.29.7': - resolution: - { - integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==, - } - engines: { node: '>=6.0.0' } + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} hasBin: true '@babel/types@7.29.0': - resolution: - { - integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} '@babel/types@7.29.7': - resolution: - { - integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@1.0.2': - resolution: - { - integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} '@emnapi/core@1.11.1': - resolution: - { - integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==, - } + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} '@emnapi/runtime@1.11.1': - resolution: - { - integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==, - } + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} '@emnapi/wasi-threads@1.2.2': - resolution: - { - integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==, - } + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} '@eslint-community/eslint-utils@4.9.1': - resolution: - { - integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 '@eslint-community/regexpp@4.12.2': - resolution: - { - integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.23.5': - resolution: - { - integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/config-helpers@0.6.0': - resolution: - { - integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/core@1.2.1': - resolution: - { - integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/js@10.0.1': - resolution: - { - integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: eslint: ^10.0.0 peerDependenciesMeta: @@ -192,245 +155,167 @@ packages: optional: true '@eslint/object-schema@3.0.5': - resolution: - { - integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/plugin-kit@0.7.2': - resolution: - { - integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@humanfs/core@0.19.1': - resolution: - { - integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} '@humanfs/node@0.16.7': - resolution: - { - integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==, - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': - resolution: - { - integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, - } - engines: { node: '>=12.22' } + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} '@humanwhocodes/retry@0.4.3': - resolution: - { - integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, - } - engines: { node: '>=18.18' } + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} '@jridgewell/gen-mapping@0.3.13': - resolution: - { - integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, - } + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} '@jridgewell/resolve-uri@3.1.2': - resolution: - { - integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, - } - engines: { node: '>=6.0.0' } + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} '@jridgewell/source-map@0.3.11': - resolution: - { - integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==, - } + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} '@jridgewell/sourcemap-codec@1.5.5': - resolution: - { - integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, - } + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} '@jridgewell/trace-mapping@0.3.31': - resolution: - { - integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, - } + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@napi-rs/wasm-runtime@1.1.6': - resolution: - { - integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==, - } + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@noble/ciphers@1.3.0': + resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.9.1': + resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@oxc-project/types@0.139.0': - resolution: - { - integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==, - } + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} '@rolldown/binding-android-arm64@1.1.5': - resolution: - { - integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] '@rolldown/binding-darwin-arm64@1.1.5': - resolution: - { - integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] '@rolldown/binding-darwin-x64@1.1.5': - resolution: - { - integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] '@rolldown/binding-freebsd-x64@1.1.5': - resolution: - { - integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - resolution: - { - integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] '@rolldown/binding-linux-arm64-gnu@1.1.5': - resolution: - { - integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.1.5': - resolution: - { - integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.1.5': - resolution: - { - integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.1.5': - resolution: - { - integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.1.5': - resolution: - { - integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] '@rolldown/binding-linux-x64-musl@1.1.5': - resolution: - { - integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] '@rolldown/binding-openharmony-arm64@1.1.5': - resolution: - { - integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] '@rolldown/binding-wasm32-wasi@1.1.5': - resolution: - { - integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] '@rolldown/binding-win32-arm64-msvc@1.1.5': - resolution: - { - integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] '@rolldown/binding-win32-x64-msvc@1.1.5': - resolution: - { - integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] '@rolldown/pluginutils@1.0.1': - resolution: - { - integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==, - } + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} '@rollup/plugin-commonjs@29.0.3': - resolution: - { - integrity: sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==, - } - engines: { node: '>=16.0.0 || 14 >= 14.17' } + resolution: {integrity: sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 peerDependenciesMeta: @@ -438,11 +323,8 @@ packages: optional: true '@rollup/plugin-node-resolve@16.0.3': - resolution: - { - integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==, - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} + engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.78.0||^3.0.0||^4.0.0 peerDependenciesMeta: @@ -450,11 +332,8 @@ packages: optional: true '@rollup/plugin-terser@1.0.0': - resolution: - { - integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==, - } - engines: { node: '>=20.0.0' } + resolution: {integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==} + engines: {node: '>=20.0.0'} peerDependencies: rollup: ^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: @@ -462,11 +341,8 @@ packages: optional: true '@rollup/plugin-typescript@12.3.0': - resolution: - { - integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==, - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==} + engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.14.0||^3.0.0||^4.0.0 tslib: '*' @@ -478,11 +354,8 @@ packages: optional: true '@rollup/pluginutils@5.3.0': - resolution: - { - integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==, - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: @@ -490,372 +363,243 @@ packages: optional: true '@rollup/rollup-android-arm-eabi@4.62.2': - resolution: - { - integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==, - } + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.62.2': - resolution: - { - integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==, - } + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.62.2': - resolution: - { - integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==, - } + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.62.2': - resolution: - { - integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==, - } + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.62.2': - resolution: - { - integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==, - } + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.62.2': - resolution: - { - integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==, - } + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.62.2': - resolution: - { - integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==, - } + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} cpu: [arm] os: [linux] libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.62.2': - resolution: - { - integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==, - } + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} cpu: [arm] os: [linux] libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.62.2': - resolution: - { - integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==, - } + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} cpu: [arm64] os: [linux] libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.62.2': - resolution: - { - integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==, - } + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} cpu: [arm64] os: [linux] libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.62.2': - resolution: - { - integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==, - } + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} cpu: [loong64] os: [linux] libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.62.2': - resolution: - { - integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==, - } + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} cpu: [loong64] os: [linux] libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.62.2': - resolution: - { - integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==, - } + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} cpu: [ppc64] os: [linux] libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.62.2': - resolution: - { - integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==, - } + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} cpu: [ppc64] os: [linux] libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.62.2': - resolution: - { - integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==, - } + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} cpu: [riscv64] os: [linux] libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.62.2': - resolution: - { - integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==, - } + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} cpu: [riscv64] os: [linux] libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.62.2': - resolution: - { - integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==, - } + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} cpu: [s390x] os: [linux] libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.62.2': - resolution: - { - integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==, - } + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} cpu: [x64] os: [linux] libc: [glibc] '@rollup/rollup-linux-x64-musl@4.62.2': - resolution: - { - integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==, - } + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} cpu: [x64] os: [linux] libc: [musl] '@rollup/rollup-openbsd-x64@4.62.2': - resolution: - { - integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==, - } + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} cpu: [x64] os: [openbsd] '@rollup/rollup-openharmony-arm64@4.62.2': - resolution: - { - integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==, - } + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} cpu: [arm64] os: [openharmony] '@rollup/rollup-win32-arm64-msvc@4.62.2': - resolution: - { - integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==, - } + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.62.2': - resolution: - { - integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==, - } + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-gnu@4.62.2': - resolution: - { - integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==, - } + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} cpu: [x64] os: [win32] '@rollup/rollup-win32-x64-msvc@4.62.2': - resolution: - { - integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==, - } + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} cpu: [x64] os: [win32] + '@scure/base@1.2.6': + resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} + + '@scure/bip32@1.7.0': + resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} + + '@scure/bip39@1.6.0': + resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} + '@standard-schema/spec@1.1.0': - resolution: - { - integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==, - } + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} '@tybys/wasm-util@0.10.3': - resolution: - { - integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==, - } + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} '@types/chai@5.2.3': - resolution: - { - integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==, - } + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/deep-eql@4.0.2': - resolution: - { - integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, - } + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/esrecurse@4.3.1': - resolution: - { - integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==, - } + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} '@types/estree@1.0.8': - resolution: - { - integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, - } + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/estree@1.0.9': - resolution: - { - integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==, - } + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} '@types/json-schema@7.0.15': - resolution: - { - integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, - } + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/node@26.1.1': - resolution: - { - integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==, - } + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} '@types/resolve@1.20.2': - resolution: - { - integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==, - } + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} '@typescript-eslint/eslint-plugin@8.64.0': - resolution: - { - integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.64.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/parser@8.64.0': - resolution: - { - integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/project-service@8.64.0': - resolution: - { - integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/scope-manager@8.64.0': - resolution: - { - integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/tsconfig-utils@8.64.0': - resolution: - { - integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/type-utils@8.64.0': - resolution: - { - integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/types@8.64.0': - resolution: - { - integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@8.64.0': - resolution: - { - integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/utils@8.64.0': - resolution: - { - integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/visitor-keys@8.64.0': - resolution: - { - integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/coverage-v8@4.1.10': - resolution: - { - integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==, - } + resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} peerDependencies: '@vitest/browser': 4.1.10 vitest: 4.1.10 @@ -864,16 +608,10 @@ packages: optional: true '@vitest/expect@4.1.10': - resolution: - { - integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==, - } + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} '@vitest/mocker@4.1.10': - resolution: - { - integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==, - } + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -884,163 +622,111 @@ packages: optional: true '@vitest/pretty-format@4.1.10': - resolution: - { - integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==, - } + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} '@vitest/runner@4.1.10': - resolution: - { - integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==, - } + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} '@vitest/snapshot@4.1.10': - resolution: - { - integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==, - } + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} '@vitest/spy@4.1.10': - resolution: - { - integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==, - } + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} '@vitest/utils@4.1.10': - resolution: - { - integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==, - } + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + + '@x402/core@2.18.0': + resolution: {integrity: sha512-3LB5m0Yx7C38ks8jDqTGYPZ2FnLzlH9pTlGvE8er2ujS1ri12sXXvWwnmYsmh3ZkXSDbV3BKU8oRKULatPp0Hg==} + + '@x402/evm@2.18.0': + resolution: {integrity: sha512-iiA5zqqJMcFdMEO+nvdctiWHcSn1EBpN8Dic0beGoxmoWX2Y8DnDHEROL/E0S3bUFpQlwTKkp4rie4wvqhsBvg==} + + '@x402/fetch@2.18.0': + resolution: {integrity: sha512-yuFLM8pIOWUNmbRwQpnO1az4IauW7dJ3yNDJkMkUkMXHLWLMeFwSwsqsRYBG7DEXcQCnb6iKY4Wps/qXUrcYOQ==} + + abitype@1.2.3: + resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true acorn-jsx@5.3.2: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, - } + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.16.0: - resolution: - { - integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==, - } - engines: { node: '>=0.4.0' } + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} hasBin: true ajv@6.14.0: - resolution: - { - integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==, - } + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} ansi-escapes@7.3.0: - resolution: - { - integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} ansi-regex@6.2.2: - resolution: - { - integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} ansi-styles@6.2.3: - resolution: - { - integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} assertion-error@2.0.1: - resolution: - { - integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} ast-v8-to-istanbul@1.0.5: - resolution: - { - integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==, - } + resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} balanced-match@4.0.4: - resolution: - { - integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==, - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} brace-expansion@5.0.7: - resolution: - { - integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==, - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} + engines: {node: 18 || 20 || >=22} buffer-from@1.1.2: - resolution: - { - integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, - } + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} chai@6.2.2: - resolution: - { - integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} cli-cursor@5.0.0: - resolution: - { - integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} cli-truncate@5.2.0: - resolution: - { - integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==, - } - engines: { node: '>=20' } + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} commander@2.20.3: - resolution: - { - integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, - } + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} commondir@1.0.1: - resolution: - { - integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==, - } + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} convert-source-map@2.0.0: - resolution: - { - integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, - } + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cross-spawn@7.0.6: - resolution: - { - integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} debug@4.4.3: - resolution: - { - integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, - } - engines: { node: '>=6.0' } + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -1048,78 +734,45 @@ packages: optional: true deep-is@0.1.4: - resolution: - { - integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, - } + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} deepmerge@4.3.1: - resolution: - { - integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} detect-libc@2.1.2: - resolution: - { - integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} emoji-regex@10.6.0: - resolution: - { - integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, - } + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} environment@1.1.0: - resolution: - { - integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} es-module-lexer@2.3.1: - resolution: - { - integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==, - } + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} escape-string-regexp@4.0.0: - resolution: - { - integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} eslint-scope@9.1.2: - resolution: - { - integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: - resolution: - { - integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-visitor-keys@5.0.1: - resolution: - { - integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint@10.7.0: - resolution: - { - integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -1128,89 +781,53 @@ packages: optional: true espree@11.2.0: - resolution: - { - integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esquery@1.7.0: - resolution: - { - integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==, - } - engines: { node: '>=0.10' } + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} esrecurse@4.3.0: - resolution: - { - integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} estraverse@5.3.0: - resolution: - { - integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} estree-walker@2.0.2: - resolution: - { - integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, - } + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} estree-walker@3.0.3: - resolution: - { - integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, - } + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} esutils@2.0.3: - resolution: - { - integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} eventemitter3@5.0.4: - resolution: - { - integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, - } + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} expect-type@1.4.0: - resolution: - { - integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==, - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} fast-deep-equal@3.1.3: - resolution: - { - integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, - } + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-json-stable-stringify@2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, - } + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: - resolution: - { - integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, - } + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} fdir@6.5.0: - resolution: - { - integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: '>=4.0.4' peerDependenciesMeta: @@ -1218,789 +835,480 @@ packages: optional: true file-entry-cache@8.0.0: - resolution: - { - integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, - } - engines: { node: '>=16.0.0' } + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} find-up@5.0.0: - resolution: - { - integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} flat-cache@4.0.1: - resolution: - { - integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, - } - engines: { node: '>=16' } + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.4.2: - resolution: - { - integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==, - } + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} fsevents@2.3.3: - resolution: - { - integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] function-bind@1.1.2: - resolution: - { - integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, - } + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} get-east-asian-width@1.5.0: - resolution: - { - integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} glob-parent@6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, - } - engines: { node: '>=10.13.0' } + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} has-flag@4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} hasown@2.0.2: - resolution: - { - integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} html-escaper@2.0.2: - resolution: - { - integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, - } + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} husky@9.1.7: - resolution: - { - integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} hasBin: true ignore@5.3.2: - resolution: - { - integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} ignore@7.0.5: - resolution: - { - integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} imurmurhash@0.1.4: - resolution: - { - integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, - } - engines: { node: '>=0.8.19' } + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} is-core-module@2.16.1: - resolution: - { - integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} is-extglob@2.1.1: - resolution: - { - integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} is-fullwidth-code-point@5.1.0: - resolution: - { - integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} is-glob@4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-module@1.0.0: - resolution: - { - integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==, - } + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} is-reference@1.2.1: - resolution: - { - integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==, - } + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} isexe@2.0.0: - resolution: - { - integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, - } + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isows@1.0.7: + resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} + peerDependencies: + ws: '*' istanbul-lib-coverage@3.2.2: - resolution: - { - integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} istanbul-lib-report@3.0.1: - resolution: - { - integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} istanbul-reports@3.2.0: - resolution: - { - integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} js-tokens@10.0.0: - resolution: - { - integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==, - } + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} json-buffer@3.0.1: - resolution: - { - integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, - } + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-schema-traverse@0.4.1: - resolution: - { - integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, - } + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: - resolution: - { - integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, - } + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} keyv@4.5.4: - resolution: - { - integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, - } + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} levn@0.4.1: - resolution: - { - integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} lightningcss-android-arm64@1.32.0: - resolution: - { - integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] lightningcss-darwin-arm64@1.32.0: - resolution: - { - integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] lightningcss-darwin-x64@1.32.0: - resolution: - { - integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] lightningcss-freebsd-x64@1.32.0: - resolution: - { - integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: - { - integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] lightningcss-linux-arm64-gnu@1.32.0: - resolution: - { - integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: - resolution: - { - integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] lightningcss-linux-x64-gnu@1.32.0: - resolution: - { - integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] lightningcss-linux-x64-musl@1.32.0: - resolution: - { - integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: - resolution: - { - integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] lightningcss-win32-x64-msvc@1.32.0: - resolution: - { - integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] lightningcss@1.32.0: - resolution: - { - integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} lint-staged@17.0.8: - resolution: - { - integrity: sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==, - } - engines: { node: '>=22.22.1' } + resolution: {integrity: sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==} + engines: {node: '>=22.22.1'} hasBin: true listr2@10.2.2: - resolution: - { - integrity: sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==, - } - engines: { node: '>=22.13.0' } + resolution: {integrity: sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==} + engines: {node: '>=22.13.0'} locate-path@6.0.0: - resolution: - { - integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} log-update@6.1.0: - resolution: - { - integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} magic-string@0.30.21: - resolution: - { - integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, - } + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magicast@0.5.3: - resolution: - { - integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==, - } + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} make-dir@4.0.0: - resolution: - { - integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} mimic-function@5.0.1: - resolution: - { - integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} minimatch@10.2.4: - resolution: - { - integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==, - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} ms@2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, - } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} nanoid@3.3.16: - resolution: - { - integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==, - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true natural-compare@1.4.0: - resolution: - { - integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, - } + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} obug@2.1.3: - resolution: - { - integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==, - } - engines: { node: '>=12.20.0' } + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} onetime@7.0.0: - resolution: - { - integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} optionator@0.9.4: - resolution: - { - integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ox@0.14.30: + resolution: {integrity: sha512-LI11uu+8iiM1B3CLckgd++YF1a0A2k5wDoM9ZeQMiL21BOzQs6L//BLS6hb1HSEKCyycdDIQLsVQx9MjpcC0hA==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true p-limit@3.1.0: - resolution: - { - integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} p-locate@5.0.0: - resolution: - { - integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} path-exists@4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} path-key@3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} path-parse@1.0.7: - resolution: - { - integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, - } + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} pathe@2.0.3: - resolution: - { - integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, - } + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} picocolors@1.1.1: - resolution: - { - integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, - } + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@4.0.5: - resolution: - { - integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} postcss@8.5.19: - resolution: - { - integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==, - } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} + engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: - resolution: - { - integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} prettier@3.9.5: - resolution: - { - integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==, - } - engines: { node: '>=14' } + resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} + engines: {node: '>=14'} hasBin: true punycode@2.3.1: - resolution: - { - integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} resolve@1.22.11: - resolution: - { - integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} hasBin: true restore-cursor@5.1.0: - resolution: - { - integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} rfdc@1.4.1: - resolution: - { - integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==, - } + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} rolldown@1.1.5: - resolution: - { - integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true rollup@4.62.2: - resolution: - { - integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==, - } - engines: { node: '>=18.0.0', npm: '>=8.0.0' } + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true semver@7.7.4: - resolution: - { - integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} hasBin: true serialize-javascript@7.0.7: - resolution: - { - integrity: sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==, - } - engines: { node: '>=20.0.0' } + resolution: {integrity: sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==} + engines: {node: '>=20.0.0'} shebang-command@2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} shebang-regex@3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} siginfo@2.0.0: - resolution: - { - integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, - } + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} signal-exit@4.1.0: - resolution: - { - integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, - } - engines: { node: '>=14' } + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} slice-ansi@7.1.2: - resolution: - { - integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} slice-ansi@8.0.0: - resolution: - { - integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==, - } - engines: { node: '>=20' } + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} smob@1.6.1: - resolution: - { - integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==, - } - engines: { node: '>=20.0.0' } + resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==} + engines: {node: '>=20.0.0'} source-map-js@1.2.1: - resolution: - { - integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} source-map-support@0.5.21: - resolution: - { - integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, - } + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} source-map@0.6.1: - resolution: - { - integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} stackback@0.0.2: - resolution: - { - integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, - } + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@4.2.0: - resolution: - { - integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==, - } + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} string-argv@0.3.2: - resolution: - { - integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==, - } - engines: { node: '>=0.6.19' } + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} string-width@7.2.0: - resolution: - { - integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} string-width@8.2.0: - resolution: - { - integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==, - } - engines: { node: '>=20' } + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + engines: {node: '>=20'} strip-ansi@7.2.0: - resolution: - { - integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} supports-color@7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: - resolution: - { - integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} terser@5.46.0: - resolution: - { - integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} + engines: {node: '>=10'} hasBin: true tinybench@2.9.0: - resolution: - { - integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, - } + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} tinyexec@1.0.2: - resolution: - { - integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} tinyexec@1.2.4: - resolution: - { - integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} tinyglobby@0.2.15: - resolution: - { - integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} tinyglobby@0.2.17: - resolution: - { - integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==, - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} tinyrainbow@3.1.0: - resolution: - { - integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==, - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} ts-api-utils@2.5.0: - resolution: - { - integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==, - } - engines: { node: '>=18.12' } + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' tslib@2.8.1: - resolution: - { - integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, - } + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-check@0.4.0: - resolution: - { - integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} typescript-eslint@8.64.0: - resolution: - { - integrity: sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' typescript@6.0.3: - resolution: - { - integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==, - } - engines: { node: '>=14.17' } + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} hasBin: true undici-types@8.3.0: - resolution: - { - integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==, - } + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} uri-js@4.4.1: - resolution: - { - integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, - } + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + viem@2.55.2: + resolution: {integrity: sha512-XlJeyNAZ96dQfOHlxLTK1FKgtWw/TtxENKNMBSBgxqALjiWiBWrFmSSzwwMivryKnBwkbt5E+90jSCLnVEilLA==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true vite@8.1.4: - resolution: - { - integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 @@ -2042,11 +1350,8 @@ packages: optional: true vitest@4.1.10: - resolution: - { - integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==, - } - engines: { node: ^20.0.0 || ^22.0.0 || >=24.0.0 } + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' @@ -2086,64 +1391,58 @@ packages: optional: true which@2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true why-is-node-running@2.3.0: - resolution: - { - integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} hasBin: true word-wrap@1.2.5: - resolution: - { - integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} wrap-ansi@10.0.0: - resolution: - { - integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==, - } - engines: { node: '>=20' } + resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + engines: {node: '>=20'} wrap-ansi@9.0.2: - resolution: - { - integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true yaml@2.9.0: - resolution: - { - integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==, - } - engines: { node: '>= 14.6' } + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} hasBin: true yocto-queue@0.1.0: - resolution: - { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} zod@4.4.3: - resolution: - { - integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==, - } + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} snapshots: + + '@adraffy/ens-normalize@1.11.1': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-string-parser@7.29.7': {} @@ -2255,6 +1554,14 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@noble/ciphers@1.3.0': {} + + '@noble/curves@1.9.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + '@oxc-project/types@0.139.0': {} '@rolldown/binding-android-arm64@1.1.5': @@ -2430,6 +1737,19 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true + '@scure/base@1.2.6': {} + + '@scure/bip32@1.7.0': + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + + '@scure/bip39@1.6.0': + dependencies: + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + '@standard-schema/spec@1.1.0': {} '@tybys/wasm-util@0.10.3': @@ -2604,6 +1924,34 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@x402/core@2.18.0': + dependencies: + zod: 3.25.76 + + '@x402/evm@2.18.0(typescript@6.0.3)': + dependencies: + '@x402/core': 2.18.0 + viem: 2.55.2(typescript@6.0.3)(zod@3.25.76) + zod: 3.25.76 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + + '@x402/fetch@2.18.0': + dependencies: + '@x402/core': 2.18.0 + + abitype@1.2.3(typescript@6.0.3)(zod@3.25.76): + optionalDependencies: + typescript: 6.0.3 + zod: 3.25.76 + + abitype@1.2.3(typescript@6.0.3)(zod@4.4.3): + optionalDependencies: + typescript: 6.0.3 + zod: 4.4.3 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -2752,6 +2100,8 @@ snapshots: esutils@2.0.3: {} + eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} expect-type@1.4.0: {} @@ -2831,6 +2181,10 @@ snapshots: isexe@2.0.0: {} + isows@1.0.7(ws@8.21.0): + dependencies: + ws: 8.21.0 + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -2980,6 +2334,36 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ox@0.14.30(typescript@6.0.3)(zod@3.25.76): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@6.0.3)(zod@3.25.76) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - zod + + ox@0.14.30(typescript@6.0.3)(zod@4.4.3): + dependencies: + '@adraffy/ens-normalize': 1.11.1 + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@6.0.3)(zod@4.4.3) + eventemitter3: 5.0.1 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - zod + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -3193,6 +2577,40 @@ snapshots: dependencies: punycode: 2.3.1 + viem@2.55.2(typescript@6.0.3)(zod@3.25.76): + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@6.0.3)(zod@3.25.76) + isows: 1.0.7(ws@8.21.0) + ox: 0.14.30(typescript@6.0.3)(zod@3.25.76) + ws: 8.21.0 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + + viem@2.55.2(typescript@6.0.3)(zod@4.4.3): + dependencies: + '@noble/curves': 1.9.1 + '@noble/hashes': 1.8.0 + '@scure/bip32': 1.7.0 + '@scure/bip39': 1.6.0 + abitype: 1.2.3(typescript@6.0.3)(zod@4.4.3) + isows: 1.0.7(ws@8.21.0) + ox: 0.14.30(typescript@6.0.3)(zod@4.4.3) + ws: 8.21.0 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + vite@8.1.4(@types/node@26.1.1)(terser@5.46.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 @@ -3257,9 +2675,13 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.2.0 + ws@8.21.0: {} + yaml@2.9.0: optional: true yocto-queue@0.1.0: {} + zod@3.25.76: {} + zod@4.4.3: {} diff --git a/src/errors.ts b/src/errors.ts index a06f461..4997c50 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,6 +1,7 @@ const STATUS_MESSAGES: Record = { 400: 'Bad request', 401: 'Invalid or missing API key', + 402: 'Payment required — if using x402, the payment did not complete (check the wallet holds enough USDC on Base and the price is within maxPaymentPerCall); otherwise pass an x402-capable fetch (see glassnode-api/x402)', 403: 'Access forbidden — check your API tier', 404: 'Endpoint or metric not found', 429: 'Rate limit exceeded', @@ -9,13 +10,16 @@ const STATUS_MESSAGES: Record = { export class GlassnodeApiError extends Error { readonly status: number; readonly statusText: string; + /** Server-provided error detail parsed from the response body, if any. */ + readonly detail?: string; - constructor(status: number, statusText: string) { - const detail = STATUS_MESSAGES[status] ?? statusText; - super(`API request failed (${status}): ${detail}`); + constructor(status: number, statusText: string, detail?: string) { + const base = STATUS_MESSAGES[status] ?? statusText; + super(`API request failed (${status}): ${base}${detail ? ` — ${detail}` : ''}`); this.name = 'GlassnodeApiError'; this.status = status; this.statusText = statusText; + this.detail = detail; } get isRetryable(): boolean { diff --git a/src/glassnode-api.ts b/src/glassnode-api.ts index e5132bc..65f8b08 100644 --- a/src/glassnode-api.ts +++ b/src/glassnode-api.ts @@ -1,4 +1,11 @@ -import { GlassnodeConfig, GlassnodeConfigSchema, Logger, FetchFn } from './types/config'; +import { + GlassnodeConfig, + GlassnodeConfigSchema, + Logger, + FetchFn, + DEFAULT_API_URL, + X402_API_URL, +} from './types/config'; import { GlassnodeApiError } from './errors'; import { AssetMetadataResponse, @@ -11,11 +18,16 @@ import { BulkResponseSchema, } from './types/metadata'; +/** Mask the `api_key` query-param value so it never reaches logs. */ +function redactApiKey(url: string): string { + return url.replace(/([?&]api_key=)[^&]+/gi, '$1***'); +} + /** * Glassnode API client */ export class GlassnodeAPI { - private apiKey: string; + private apiKey: string | undefined; private apiUrl: string; private logger?: Logger; private fetchFn: FetchFn; @@ -31,7 +43,7 @@ export class GlassnodeAPI { const validatedConfig = GlassnodeConfigSchema.parse(config); this.apiKey = validatedConfig.apiKey; - this.apiUrl = validatedConfig.apiUrl; + this.apiUrl = validatedConfig.apiUrl ?? (validatedConfig.x402 ? X402_API_URL : DEFAULT_API_URL); this.logger = validatedConfig.logger as Logger | undefined; this.fetchFn = (validatedConfig.fetch as FetchFn) ?? globalThis.fetch; this.maxRetries = validatedConfig.maxRetries; @@ -47,7 +59,7 @@ export class GlassnodeAPI { private async request(endpoint: string, params: Record = {}): Promise { const queryParams = new URLSearchParams({ ...params, - api_key: this.apiKey, + ...(this.apiKey ? { api_key: this.apiKey } : {}), }); const url = `${this.apiUrl}${endpoint}?${queryParams}`; @@ -60,7 +72,7 @@ export class GlassnodeAPI { await new Promise((resolve) => setTimeout(resolve, delay)); } - this.logger?.('API call:', url); + this.logger?.('API call:', redactApiKey(url)); try { const response = await this.fetchFn(url); @@ -71,7 +83,11 @@ export class GlassnodeAPI { lastError = error; continue; } - throw error; + // Surface the server's error body (e.g. "Resolution 1h is not allowed") in the message. + const detail = await this.readErrorDetail(response); + throw detail + ? new GlassnodeApiError(response.status, response.statusText, detail) + : error; } return await response.json(); @@ -91,6 +107,29 @@ export class GlassnodeAPI { throw lastError; } + /** + * Best-effort extraction of a human-readable message from an error response body. + * Glassnode returns `{ "message": "..." }` (or `{ "error": "..." }`) on failures. + * Never throws — returns undefined if the body is empty or unreadable. + */ + private async readErrorDetail(response: Response): Promise { + try { + const text = await response.text(); + if (!text.trim()) return undefined; + try { + const parsed = JSON.parse(text); + const message = parsed?.message ?? parsed?.error; + // Valid JSON: only use a string message/error — never dump the raw JSON (e.g. "null"). + return typeof message === 'string' && message.trim() ? message.trim() : undefined; + } catch { + // Non-JSON body — return the raw text. + return text.trim().slice(0, 300); + } + } catch { + return undefined; + } + } + /** * Get metadata for all assets * @returns Promise resolving to validated asset metadata diff --git a/src/types/config.ts b/src/types/config.ts index 9b5b2cf..603e5e4 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -10,45 +10,47 @@ export type Logger = (message: string, ...args: unknown[]) => void; */ export type FetchFn = typeof fetch; +/** Default free Glassnode API base URL. */ +export const DEFAULT_API_URL = 'https://api.glassnode.com'; +/** x402 (paid) Glassnode API base URL — Base mainnet. */ +export const X402_API_URL = 'https://x402.glassnode.com'; +// A testnet/staging x402 endpoint is not hardcoded here — pass its URL via the `apiUrl` config option. + /** * Zod schema for Glassnode API configuration */ -export const GlassnodeConfigSchema = z.object({ - /** - * API key for authentication - */ - apiKey: z.string().min(1, 'API key is required'), - - /** - * Base URL for the Glassnode API - * @default "https://api.glassnode.com" - */ - apiUrl: z.string().url().default('https://api.glassnode.com'), - - /** - * Optional logger for API call debugging - * @example { logger: console.log } - */ - logger: z.function().optional(), - - /** - * Optional custom fetch function (e.g. for custom headers, retries, or testing) - * @default globalThis.fetch - */ - fetch: z.function().optional(), - - /** - * Maximum number of retries for retryable errors (429 and 5xx) - * @default 0 (no retries) - */ - maxRetries: z.number().int().nonnegative().default(0), - - /** - * Base delay in milliseconds between retries (doubles each attempt) - * @default 1000 - */ - retryDelay: z.number().int().positive().default(1000), -}); +export const GlassnodeConfigSchema = z + .object({ + /** API key for authentication. Required unless `x402` is enabled. */ + apiKey: z.string().min(1, 'API key is required').optional(), + + /** Base URL for the Glassnode API. An explicit value always wins over the `x402` preset. */ + apiUrl: z.string().url().optional(), + + /** Route requests through the x402 paid endpoint (`https://x402.glassnode.com`). */ + x402: z.boolean().default(false), + + /** Optional logger for API call debugging. */ + logger: z.function().optional(), + + /** Optional custom fetch function (e.g. an x402-wrapped fetch, or for testing). */ + fetch: z.function().optional(), + + /** Maximum number of retries for retryable errors (429 and 5xx). */ + maxRetries: z.number().int().nonnegative().default(0), + + /** Base delay in milliseconds between retries (doubles each attempt). */ + retryDelay: z.number().int().positive().default(1000), + }) + .refine((c) => c.x402 || (c.apiKey !== undefined && c.apiKey.length > 0), { + message: 'apiKey is required unless x402 is enabled', + path: ['apiKey'], + }) + .refine((c) => !c.x402 || c.fetch !== undefined, { + message: + 'fetch is required when x402 is enabled — pass an x402-capable fetch (see glassnode-api/x402)', + path: ['fetch'], + }); /** * Configuration for the Glassnode API client diff --git a/src/x402.ts b/src/x402.ts new file mode 100644 index 0000000..ef4d5aa --- /dev/null +++ b/src/x402.ts @@ -0,0 +1,73 @@ +import type { LocalAccount } from 'viem'; + +/** Options for {@link createX402Fetch}. */ +export interface X402FetchOptions { + /** viem account used to sign payment authorizations (e.g. `privateKeyToAccount(pk)`). */ + account: LocalAccount; + /** Per-call spend ceiling in USDC (decimal string). Default `'0.06'` (just above the $0.05 metric price). */ + maxPaymentPerCall?: string; + /** Base fetch to wrap. Default `globalThis.fetch`. */ + fetch?: typeof fetch; +} + +const DEFAULT_MAX_PAYMENT_PER_CALL = '0.06'; +const USDC_DECIMALS = 6; +// Base mainnet + Base Sepolia (CAIP-2). Registering both lets one wrapped fetch serve either host. +const X402_NETWORKS = ['eip155:8453', 'eip155:84532'] as const; + +/** Convert a USDC decimal string (e.g. `'0.06'`) to atomic units (6 decimals). Truncates extra decimals. */ +export function usdcDecimalToAtomic(value: string): bigint { + if (!/^\d+(\.\d+)?$/.test(value)) { + throw new Error(`Invalid USDC amount: "${value}"`); + } + const [whole, frac = ''] = value.split('.'); + const fracPadded = (frac + '0'.repeat(USDC_DECIMALS)).slice(0, USDC_DECIMALS); + return BigInt(whole) * 10n ** BigInt(USDC_DECIMALS) + BigInt(fracPadded || '0'); +} + +/** Build a payment policy that rejects any payment requirement above `maxAtomic` (atomic USDC units). */ +export function createMaxAmountPolicy(maxAtomic: bigint) { + return (_x402Version: number, requirements: { amount: string }[]): { amount: string }[] => + requirements.filter((r) => BigInt(r.amount) <= maxAtomic); +} + +/** + * Create an x402-capable `fetch` for paid Glassnode calls (Node-first). + * + * Dynamically loads the optional peer deps `@x402/fetch` + `@x402/evm`; pass the result as the + * `fetch` option of `GlassnodeAPI` together with `x402: true`. + */ +export async function createX402Fetch(options: X402FetchOptions): Promise { + const { + account, + maxPaymentPerCall = DEFAULT_MAX_PAYMENT_PER_CALL, + fetch: baseFetch = globalThis.fetch, + } = options; + + const maxAtomic = usdcDecimalToAtomic(maxPaymentPerCall); + + const [x402fetchMod, evmMod] = await Promise.all([ + import('@x402/fetch'), + import('@x402/evm'), + ]).catch((err) => { + throw new Error( + "createX402Fetch requires the optional peer dependencies '@x402/fetch', '@x402/evm', and 'viem'. Install them: pnpm add @x402/fetch @x402/evm viem", + { cause: err } + ); + }); + const { wrapFetchWithPayment, x402Client } = x402fetchMod; + const { ExactEvmScheme } = evmMod; + + let client = new x402Client(); + for (const network of X402_NETWORKS) { + client = client.register( + network, + new ExactEvmScheme(account as ConstructorParameters[0]) + ); + } + client = client.registerPolicy( + createMaxAmountPolicy(maxAtomic) as unknown as Parameters[0] + ); + + return wrapFetchWithPayment(baseFetch, client) as typeof fetch; +} diff --git a/test/config.spec.ts b/test/config.spec.ts new file mode 100644 index 0000000..8897a84 --- /dev/null +++ b/test/config.spec.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import { GlassnodeConfigSchema, DEFAULT_API_URL, X402_API_URL } from '../src/types/config'; + +describe('GlassnodeConfigSchema', () => { + it('exposes the URL constants', () => { + expect(DEFAULT_API_URL).toBe('https://api.glassnode.com'); + expect(X402_API_URL).toBe('https://x402.glassnode.com'); + }); + + it('requires apiKey when x402 is not enabled', () => { + expect(() => GlassnodeConfigSchema.parse({})).toThrow(/apiKey/); + expect(GlassnodeConfigSchema.parse({ apiKey: 'k' }).apiKey).toBe('k'); + }); + + it('allows omitting apiKey when x402 is enabled, but then requires fetch', () => { + const fetchFn = (async () => new Response()) as unknown as typeof fetch; + expect(() => GlassnodeConfigSchema.parse({ x402: true })).toThrow(/fetch/); + const parsed = GlassnodeConfigSchema.parse({ x402: true, fetch: fetchFn }); + expect(parsed.x402).toBe(true); + expect(parsed.apiKey).toBeUndefined(); + }); + + it('defaults x402 to false and apiUrl to undefined', () => { + const parsed = GlassnodeConfigSchema.parse({ apiKey: 'k' }); + expect(parsed.x402).toBe(false); + expect(parsed.apiUrl).toBeUndefined(); + }); +}); diff --git a/test/glassnode-api.spec.ts b/test/glassnode-api.spec.ts index 26471a7..5033516 100644 --- a/test/glassnode-api.spec.ts +++ b/test/glassnode-api.spec.ts @@ -61,6 +61,21 @@ describe('GlassnodeAPI', () => { expect(logger).toHaveBeenCalledWith('API call:', expect.stringContaining(DEFAULT_API_URL)); }); + it('redacts the api_key in logged URLs', async () => { + const logger = vi.fn(); + const fetchFn = createMockFetch({ + ok: true, + json: vi.fn().mockResolvedValue(mockMetricListResponse), + }); + + const api = new GlassnodeAPI({ apiKey: API_KEY, logger, fetch: fetchFn }); + await api.getMetricList(); + + const logged = logger.mock.calls.find((c) => c[0] === 'API call:')?.[1] as string; + expect(logged).toContain('api_key=***'); + expect(logged).not.toContain(API_KEY); + }); + it('should use custom fetch when provided', async () => { const fetchFn = createMockFetch({ ok: true, @@ -321,6 +336,67 @@ describe('GlassnodeAPI', () => { expect(new GlassnodeApiError(401, 'Unauthorized').isRetryable).toBe(false); }); + it('gives a helpful 402 message and marks it non-retryable', () => { + const err = new GlassnodeApiError(402, 'Payment Required'); + expect(err.message).toContain('Payment required'); + // Covers the funded-but-failed x402 case (payment did not settle), not only "no wrapper" + expect(err.message).toContain('USDC'); + expect(err.message).toContain('maxPaymentPerCall'); + expect(err.message).toContain('glassnode-api/x402'); + expect(err.isRetryable).toBe(false); + }); + + it('appends a server detail to the message and exposes it on .detail', () => { + const err = new GlassnodeApiError(403, 'Forbidden', 'Resolution 1h is not allowed'); + expect(err.message).toContain('Access forbidden'); + expect(err.message).toContain('Resolution 1h is not allowed'); + expect(err.detail).toBe('Resolution 1h is not allowed'); + }); + + it('surfaces the JSON error-body message from the server', async () => { + const fetchFn = vi.fn().mockResolvedValue({ + ok: false, + status: 403, + statusText: 'Forbidden', + text: vi.fn().mockResolvedValue( + JSON.stringify({ + message: 'Resolution 1h is not allowed. Allowed resolutions: [24h, 1w, 1month]', + }) + ), + }); + const api = createApi(fetchFn); + + await expect( + api.callMetric('/addresses/active_count', { a: 'ETH', i: '1h' }) + ).rejects.toThrow('Resolution 1h is not allowed'); + }); + + it('surfaces a plain-text error body when it is not JSON', async () => { + const fetchFn = vi.fn().mockResolvedValue({ + ok: false, + status: 400, + statusText: 'Bad Request', + text: vi.fn().mockResolvedValue('unexpected parameter "foo"'), + }); + const api = createApi(fetchFn); + + await expect(api.getMetricList()).rejects.toThrow('unexpected parameter "foo"'); + }); + + it('does not append raw JSON when the body has no message/error field', async () => { + const fetchFn = vi.fn().mockResolvedValue({ + ok: false, + status: 402, + statusText: 'Payment Required', + text: vi.fn().mockResolvedValue('null'), + }); + const api = createApi(fetchFn); + + const err = (await api.callMetric('/market/mvrv', { a: 'BTC' }).catch((e) => e)) as Error; + expect(err.message).toContain('Payment required'); + expect(err.message).not.toContain('null'); + }); + it('should handle network errors', async () => { const fetchFn = vi.fn().mockRejectedValue(new TypeError('Failed to fetch')); const api = createApi(fetchFn); @@ -442,4 +518,42 @@ describe('GlassnodeAPI', () => { expect(result).toEqual(mockMetricListResponse); }); }); + + describe('x402 mode', () => { + const okFetch = () => + vi.fn().mockResolvedValue({ + ok: true, + json: vi.fn().mockResolvedValue(mockMetricListResponse), + }); + + it('routes to the x402 host when x402 is true', async () => { + const fetchFn = okFetch(); + const api = new GlassnodeAPI({ x402: true, fetch: fetchFn }); + await api.getMetricList(); + expect(fetchFn).toHaveBeenCalledWith( + expect.stringContaining('https://x402.glassnode.com/v1/metadata/metrics') + ); + }); + + it('omits api_key when no apiKey is set', async () => { + const fetchFn = okFetch(); + const api = new GlassnodeAPI({ x402: true, fetch: fetchFn }); + await api.getMetricList(); + const calledUrl = fetchFn.mock.calls[0][0] as string; + expect(calledUrl).not.toContain('api_key'); + }); + + it('an explicit apiUrl overrides the x402 preset', async () => { + const fetchFn = okFetch(); + const api = new GlassnodeAPI({ + x402: true, + apiUrl: 'https://x402.example.test', + fetch: fetchFn, + }); + await api.getMetricList(); + expect(fetchFn).toHaveBeenCalledWith( + expect.stringContaining('https://x402.example.test/v1/metadata/metrics') + ); + }); + }); }); diff --git a/test/x402.integration.spec.ts b/test/x402.integration.spec.ts new file mode 100644 index 0000000..3bef3c8 --- /dev/null +++ b/test/x402.integration.spec.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from 'vitest'; +import { privateKeyToAccount } from 'viem/accounts'; +import { GlassnodeAPI } from '../src/glassnode-api'; +import { createX402Fetch } from '../src/x402'; + +const KEY = process.env.X402_TESTNET_PRIVATE_KEY; +// The testnet endpoint URL is supplied via env (not hardcoded); skip if absent. +const TESTNET_URL = process.env.X402_TESTNET_URL; + +// Requires a Base-Sepolia wallet funded with test USDC + the testnet endpoint URL. +describe.skipIf(!KEY || !TESTNET_URL)('x402 testnet integration', () => { + it('pays for a metric on the testnet endpoint and returns validated data', async () => { + const account = privateKeyToAccount(KEY as `0x${string}`); + const api = new GlassnodeAPI({ + x402: true, + apiUrl: TESTNET_URL, + fetch: await createX402Fetch({ account, maxPaymentPerCall: '0.06' }), + }); + + const data = await api.callMetric<{ t: number; v: number }[]>('/market/mvrv', { + a: 'BTC', + i: '24h', + }); + + expect(Array.isArray(data)).toBe(true); + expect(data.length).toBeGreaterThan(0); + expect(typeof data[0].t).toBe('number'); + expect(typeof data[0].v).toBe('number'); + }, 60_000); +}); diff --git a/test/x402.missing-deps.spec.ts b/test/x402.missing-deps.spec.ts new file mode 100644 index 0000000..19ce9a1 --- /dev/null +++ b/test/x402.missing-deps.spec.ts @@ -0,0 +1,20 @@ +import { describe, it, expect, vi } from 'vitest'; + +// Simulate the optional peer dep being absent: importing it throws. +vi.mock('@x402/evm', () => { + throw new Error('Cannot find package @x402/evm'); +}); + +describe('createX402Fetch without optional deps', () => { + it('throws a clear install error', async () => { + const { createX402Fetch } = await import('../src/x402'); + const account = { + address: '0x0000000000000000000000000000000000000001' as const, + signTypedData: async () => '0x' as const, + }; + await expect( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createX402Fetch({ account: account as any }) + ).rejects.toThrow(/optional peer dependencies/); + }); +}); diff --git a/test/x402.spec.ts b/test/x402.spec.ts new file mode 100644 index 0000000..36b4166 --- /dev/null +++ b/test/x402.spec.ts @@ -0,0 +1,41 @@ +import { describe, it, expect } from 'vitest'; +import { usdcDecimalToAtomic, createMaxAmountPolicy, createX402Fetch } from '../src/x402'; + +describe('usdcDecimalToAtomic', () => { + it('converts USDC decimals to 6-decimal atomic units', () => { + expect(usdcDecimalToAtomic('0.06')).toBe(60000n); + expect(usdcDecimalToAtomic('0.05')).toBe(50000n); + expect(usdcDecimalToAtomic('0.01')).toBe(10000n); + expect(usdcDecimalToAtomic('1')).toBe(1000000n); + expect(usdcDecimalToAtomic('0')).toBe(0n); + }); + + it('truncates beyond 6 decimals and rejects bad input', () => { + expect(usdcDecimalToAtomic('0.1234567')).toBe(123456n); + expect(() => usdcDecimalToAtomic('abc')).toThrow(/Invalid USDC amount/); + expect(() => usdcDecimalToAtomic('-1')).toThrow(/Invalid USDC amount/); + }); +}); + +describe('createMaxAmountPolicy', () => { + it('keeps only requirements at or below the cap', () => { + const policy = createMaxAmountPolicy(60000n); + const reqs = [{ amount: '50000' }, { amount: '60000' }, { amount: '70000' }]; + expect(policy(2, reqs)).toEqual([{ amount: '50000' }, { amount: '60000' }]); + }); +}); + +describe('createX402Fetch', () => { + it('returns a callable fetch using the real x402 client', async () => { + const account = { + address: '0x0000000000000000000000000000000000000001' as const, + signTypedData: async () => '0x' as const, + }; + const wrapped = await createX402Fetch({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + account: account as any, + maxPaymentPerCall: '0.06', + }); + expect(typeof wrapped).toBe('function'); + }); +}); diff --git a/tsconfig.browser.json b/tsconfig.browser.json index bfd03f8..a602afe 100644 --- a/tsconfig.browser.json +++ b/tsconfig.browser.json @@ -8,5 +8,5 @@ "declaration": false }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test", "examples"] + "exclude": ["node_modules", "dist", "test", "examples", "src/x402.ts"] } diff --git a/tsconfig.json b/tsconfig.json index f714816..d0a3083 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "outDir": "./dist", "strict": true, "forceConsistentCasingInFileNames": true, - "isolatedModules": true + "isolatedModules": true, + "types": ["node"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "test"]