diff --git a/contrib/examples/issue-302-unused-fixture-audit/README.md b/contrib/examples/issue-302-unused-fixture-audit/README.md new file mode 100644 index 0000000..c7d607e --- /dev/null +++ b/contrib/examples/issue-302-unused-fixture-audit/README.md @@ -0,0 +1,91 @@ +# Unused Fixture Audit + +Self-contained reference for issue [#302](https://github.com/Vellar-Wallet/vellar-sdk/issues/302): confirming which exports of `src/x402-test-fixtures.ts` are still referenced, and which imports of it are dead. + +## Run tests + +```bash +npx vitest run contrib/examples/issue-302-unused-fixture-audit +``` + +## Outcome: there are no unused fixtures to remove + +Issue #302 asks to remove "several fixtures that are no longer referenced by any current test file". Its first requirement is to **confirm each fixture has no remaining references** — and on this branch, that confirmation comes back negative: **every export in `src/x402-test-fixtures.ts` is still referenced by a live test.** There is nothing to delete. + +The audit below is produced by `formatAuditReport()` in this module, run against the real files: + +| Export | Kind | References | Used by | +| --- | --- | ---: | --- | +| `C_ADDRESS` | const | 2 | src/x402-client.test.ts, contrib/x402-client-fallback.test.ts | +| `TOKEN` | const | 13 | src/x402-client.test.ts, src/x402-guards.test.ts, contrib/x402-guards-boundary.test.ts | +| `PAYTO` | const | 4 | src/x402-client.test.ts | +| `SIM_SOURCE` | const | 13 | src/x402-client.test.ts, contrib/x402-client-fallback.test.ts | +| `CAIP2_TESTNET` | const | 31 | src/x402-guards.test.ts, contrib/x402-guards-boundary.test.ts | +| `b64` | function | 11 | src/x402-guards.test.ts, contrib/x402-guards-boundary.test.ts | +| `requirements` | function | 56 | src/x402-client.test.ts, src/x402-guards.test.ts, contrib/x402-client-fallback.test.ts, contrib/x402-guards-boundary.test.ts | +| `decoded` | function | 34 | src/x402-guards.test.ts, contrib/x402-guards-boundary.test.ts | +| `response402` | function | 13 | src/x402-client.test.ts, src/x402-guards.test.ts, contrib/x402-client-fallback.test.ts, contrib/x402-guards-boundary.test.ts | + +Reference counts **exclude the import line itself**, so every number above is a genuine use in a test body. + +`PAYTO` is worth calling out: it is the one export used by a single consumer, and on `main` it genuinely was imported by nobody. Tests added to `dev` since then use it, which is precisely why the audit is worth re-running rather than trusting a stale answer. + +### What the audit did find + +One real cleanup, applied in this PR: + +``` +Dead imports in contrib/x402-client-fallback.test.ts: `requirements`, `response402` +``` + +Both names were imported and never used. That is a dead *import*, not an unused *export* — the fixtures themselves are used elsewhere — so the fix is to shorten the import, not to delete the fixtures. Done in [`contrib/x402-client-fallback.test.ts`](../../x402-client-fallback.test.ts). + +## Why a tool instead of a grep + +A bare `grep -c NAME` gives the wrong answer here, in three separate ways: + +1. **It counts the import line.** A fixture that is imported and never used still looks used. This is exactly the `contrib/x402-client-fallback.test.ts` case above — grep reports `requirements` as referenced when it isn't. +2. **It counts unrelated local declarations.** `PAYTO` is independently declared in `src/x402-auth-entry.test.ts`, `packages/mcp-x402-payer/test/helpers.ts`, and others. Grepping the repo for `PAYTO` returns 13 hits that have nothing to do with the fixture module. +3. **It cannot distinguish an unused export from a dead import.** The two have different fixes — delete the export vs. shorten the import — and conflating them leads to deleting a fixture that another file still needs. + +`auditFixtures()` resolves all three by parsing each consumer's fixture import statement, then counting whole-word references in that consumer's body with the fixture import line removed. + +## Usage + +```ts +import { readFileSync } from "node:fs"; +import { auditFixtures, formatAuditReport } from "./fixture-audit"; + +const result = auditFixtures({ + fixtureSource: readFileSync("src/x402-test-fixtures.ts", "utf8"), + moduleId: "x402-test-fixtures", + consumers: ["src/x402-client.test.ts", "src/x402-guards.test.ts"].map((file) => ({ + file, + source: readFileSync(file, "utf8"), + })), +}); + +result.unusedExports; // [] — safe to delete, were there any +result.deadImports; // [{ file, names }] — imported but never referenced +console.log(formatAuditReport(result)); +``` + +## Semantics + +| Case | Result | +|------|--------| +| Export imported and referenced by a consumer | Used | +| Export imported by a consumer but never referenced | Unused export **and** a dead import for that file | +| Export referenced only inside the fixture module itself | Unused — internal use doesn't justify an `export` | +| Export referenced by at least one of several consumers | Used; still a dead import in the files that don't use it | +| A name declared locally in a file that doesn't import the module | Ignored — the file isn't a consumer | +| Longer identifier containing the name (`TOKEN` vs `TOKEN_LIST`) | Not counted; matching is whole-word | +| Aliased import (`TOKEN as ASSET`) | Counts references to the local name (`ASSET`) | + +## Guarding against regression + +The final `describe` block in the test file runs the audit against the real repository files on every `npm test`. If a future change orphans a fixture, `reports NO unused exports` fails and names it — turning "is this still used?" from a manual grep into a standing check, and flagging the moment the removal #302 asked for actually becomes correct. + +## Limits + +Line-oriented parsing, no TypeScript AST. It handles the syntax these fixtures actually use — single-line named imports, `as` aliases, inline `type` modifiers — and deliberately does not attempt multi-line import blocks for the *fixture* module, `import * as ns`, or re-exports. A name referenced only inside a comment or string literal counts as a reference. For this fixture module and its four consumers that is sufficient; for a general-purpose dead-code pass, use a tool built on the compiler API such as `knip` or `ts-prune`. diff --git a/contrib/examples/issue-302-unused-fixture-audit/fixture-audit.test.ts b/contrib/examples/issue-302-unused-fixture-audit/fixture-audit.test.ts new file mode 100644 index 0000000..fb6582b --- /dev/null +++ b/contrib/examples/issue-302-unused-fixture-audit/fixture-audit.test.ts @@ -0,0 +1,271 @@ +import { readFileSync } from "node:fs"; +import { dirname, join, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { + auditFixtures, + countReferences, + formatAuditReport, + parseFixtureExports, + parseFixtureImports, +} from "./fixture-audit"; + +const MODULE_ID = "x402-test-fixtures"; +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../.."); + +describe("parseFixtureExports", () => { + it("picks up both const and function exports, in declaration order", () => { + const source = [ + "import type { Foo } from './types';", + "export const TOKEN = 'C...';", + "export const PAYTO = 'G...';", + "export function b64(o: unknown): string { return ''; }", + ].join("\n"); + + expect(parseFixtureExports(source)).toEqual([ + { name: "TOKEN", kind: "const" }, + { name: "PAYTO", kind: "const" }, + { name: "b64", kind: "function" }, + ]); + }); + + it("ignores non-exported declarations and type-only exports", () => { + const source = [ + "const INTERNAL = 1;", + "function helper() {}", + "export type Thing = { a: number };", + "export interface Other { b: string }", + "export const REAL = 2;", + ].join("\n"); + + expect(parseFixtureExports(source)).toEqual([{ name: "REAL", kind: "const" }]); + }); +}); + +describe("parseFixtureImports", () => { + it("reads the named bindings off the fixture import line", () => { + const source = [ + "import { describe, it } from 'vitest';", + "import { TOKEN, b64, requirements } from './x402-test-fixtures';", + ].join("\n"); + + expect(parseFixtureImports(source, MODULE_ID)).toEqual(["TOKEN", "b64", "requirements"]); + }); + + it("returns the local name for aliased imports, since that is what the body uses", () => { + const source = "import { TOKEN as ASSET, b64 } from '../src/x402-test-fixtures';"; + expect(parseFixtureImports(source, MODULE_ID)).toEqual(["ASSET", "b64"]); + }); + + it("strips inline `type` modifiers", () => { + const source = "import { type Requirements, TOKEN } from './x402-test-fixtures';"; + expect(parseFixtureImports(source, MODULE_ID)).toEqual(["Requirements", "TOKEN"]); + }); + + it("returns nothing when the file does not import the fixture module", () => { + const source = "import { describe, it } from 'vitest';\nconst TOKEN = 'local';"; + expect(parseFixtureImports(source, MODULE_ID)).toEqual([]); + }); + + it("is not fooled by a longer import list appearing earlier in the file", () => { + // Regression guard: a lazy `[\s\S]*?` regex spans from the FIRST import in + // the file to the fixture specifier, capturing every intervening binding. + const source = [ + "import { describe, expect, it, vi } from 'vitest';", + "import {", + " DisallowedAssetError,", + " InvalidRequirementsError,", + "} from './x402-types';", + "import { TOKEN, PAYTO } from './x402-test-fixtures';", + ].join("\n"); + + expect(parseFixtureImports(source, MODULE_ID)).toEqual(["TOKEN", "PAYTO"]); + }); +}); + +describe("countReferences", () => { + it("does not count the fixture import line itself", () => { + const source = ["import { TOKEN } from './x402-test-fixtures';", "const other = 1;"].join("\n"); + expect(countReferences(source, "TOKEN", MODULE_ID)).toBe(0); + }); + + it("counts real uses in the body", () => { + const source = [ + "import { TOKEN } from './x402-test-fixtures';", + "it('works', () => {", + " expect(TOKEN).toBe(TOKEN);", + "});", + ].join("\n"); + + expect(countReferences(source, "TOKEN", MODULE_ID)).toBe(2); + }); + + it("matches whole words only, so a longer identifier does not count", () => { + const source = ["const TOKEN_LIST = [];", "const MY_TOKEN = 1;", "use(TOKEN);"].join("\n"); + expect(countReferences(source, "TOKEN", MODULE_ID)).toBe(1); + }); +}); + +describe("auditFixtures", () => { + const fixtureSource = [ + "export const USED = 'a';", + "export const DEAD = 'b';", + "export function helper() { return DEAD; }", + ].join("\n"); + + it("flags an export that nobody imports", () => { + const result = auditFixtures({ + fixtureSource, + moduleId: MODULE_ID, + consumers: [ + { + file: "a.test.ts", + source: "import { USED, helper } from './x402-test-fixtures';\nuse(USED); helper();", + }, + ], + }); + + expect(result.unusedExports).toEqual(["DEAD"]); + }); + + it("does NOT count a fixture's use inside the fixture module itself as a reference", () => { + // `helper()` references DEAD, but that is internal to the fixture module. + // An export used only internally still shouldn't be exported. + const result = auditFixtures({ + fixtureSource, + moduleId: MODULE_ID, + consumers: [{ file: "a.test.ts", source: "import { USED } from './x402-test-fixtures';\nuse(USED);" }], + }); + + expect(result.unusedExports).toContain("DEAD"); + }); + + it("treats an imported-but-unreferenced export as unused, not used", () => { + const result = auditFixtures({ + fixtureSource, + moduleId: MODULE_ID, + consumers: [ + // DEAD is imported but never referenced — the exact case a bare grep + // gets wrong. + { file: "a.test.ts", source: "import { USED, DEAD } from './x402-test-fixtures';\nuse(USED);" }, + ], + }); + + // `helper` is unused too — no consumer imports it at all. + expect(result.unusedExports).toEqual(["DEAD", "helper"]); + expect(result.deadImports).toEqual([{ file: "a.test.ts", names: ["DEAD"] }]); + }); + + it("counts an export as used when ANY consumer references it", () => { + const result = auditFixtures({ + fixtureSource, + moduleId: MODULE_ID, + consumers: [ + { file: "a.test.ts", source: "import { DEAD } from './x402-test-fixtures';" }, + { file: "b.test.ts", source: "import { DEAD } from './x402-test-fixtures';\nuse(DEAD);" }, + ], + }); + + expect(result.unusedExports).not.toContain("DEAD"); + // Still reported as a dead import in the file that doesn't use it. + expect(result.deadImports).toEqual([{ file: "a.test.ts", names: ["DEAD"] }]); + }); + + it("ignores files that never import the fixture module, even if names collide", () => { + const result = auditFixtures({ + fixtureSource, + moduleId: MODULE_ID, + consumers: [ + { file: "a.test.ts", source: "import { USED } from './x402-test-fixtures';\nuse(USED);" }, + // Declares its own DEAD; must not make the fixture's DEAD look used. + { file: "unrelated.test.ts", source: "const DEAD = 'local';\nuse(DEAD); use(DEAD);" }, + ], + }); + + // The unrelated file's local `DEAD` must not rescue the fixture's `DEAD`. + expect(result.unusedExports).toEqual(["DEAD", "helper"]); + expect(result.consumers.map((c) => c.file)).toEqual(["a.test.ts"]); + }); + + it("sums references across consumers and records who imports what", () => { + const result = auditFixtures({ + fixtureSource, + moduleId: MODULE_ID, + consumers: [ + { file: "a.test.ts", source: "import { USED } from './x402-test-fixtures';\nuse(USED); use(USED);" }, + { file: "b.test.ts", source: "import { USED } from './x402-test-fixtures';\nuse(USED);" }, + ], + }); + + const used = result.verdicts.find((v) => v.name === "USED"); + expect(used?.totalReferences).toBe(3); + expect(used?.importedBy).toEqual(["a.test.ts", "b.test.ts"]); + }); +}); + +describe("formatAuditReport", () => { + it("states that nothing can be removed when every export is used", () => { + const report = formatAuditReport( + auditFixtures({ + fixtureSource: "export const USED = 'a';", + moduleId: MODULE_ID, + consumers: [{ file: "a.test.ts", source: "import { USED } from './x402-test-fixtures';\nuse(USED);" }], + }), + ); + + expect(report).toContain("nothing to remove"); + expect(report).toContain("| `USED` | const | 1 | a.test.ts |"); + }); + + it("names the removable exports when there are any", () => { + const report = formatAuditReport( + auditFixtures({ + fixtureSource: "export const USED = 'a';\nexport const DEAD = 'b';", + moduleId: MODULE_ID, + consumers: [{ file: "a.test.ts", source: "import { USED } from './x402-test-fixtures';\nuse(USED);" }], + }), + ); + + expect(report).toContain("Unused exports (safe to remove): `DEAD`"); + }); +}); + +// The audit that answers issue #302 for the tree this test runs against. +describe("src/x402-test-fixtures.ts (live audit)", () => { + const fixturePath = join(repoRoot, "src/x402-test-fixtures.ts"); + // Every file known to import the fixtures module, per a repo-wide search. + const consumerPaths = [ + "src/x402-client.test.ts", + "src/x402-guards.test.ts", + "contrib/x402-client-fallback.test.ts", + "contrib/x402-guards-boundary.test.ts", + ]; + + const result = auditFixtures({ + fixtureSource: readFileSync(fixturePath, "utf8"), + moduleId: MODULE_ID, + consumers: consumerPaths.map((p) => ({ + file: relative(repoRoot, join(repoRoot, p)).replace(/\\/g, "/"), + source: readFileSync(join(repoRoot, p), "utf8"), + })), + }); + + it("finds every consumer of the fixture module", () => { + expect(result.consumers).toHaveLength(consumerPaths.length); + }); + + it("reports NO unused exports — the premise of #302 does not hold on this branch", () => { + // If a future change orphans a fixture, this fails and names it, which is + // exactly when the removal #302 asked for becomes correct. + expect(result.unusedExports).toEqual([]); + }); + + it("confirms each individual export is referenced by at least one live test", () => { + for (const verdict of result.verdicts) { + expect( + verdict.totalReferences, + `${verdict.name} is exported but never referenced`, + ).toBeGreaterThan(0); + } + }); +}); diff --git a/contrib/examples/issue-302-unused-fixture-audit/fixture-audit.ts b/contrib/examples/issue-302-unused-fixture-audit/fixture-audit.ts new file mode 100644 index 0000000..ba1b64d --- /dev/null +++ b/contrib/examples/issue-302-unused-fixture-audit/fixture-audit.ts @@ -0,0 +1,227 @@ +/** + * Static audit of a shared test-fixture module: which exports are actually + * consumed, and which imports are dead. + * + * Contributed for issue #302, which asked to remove "several fixtures that are + * no longer referenced by any current test file" from + * `src/x402-test-fixtures.ts`. The first requirement on that issue is to + * *confirm* each fixture has no remaining references — this module is that + * confirmation step, written so the answer is reproducible instead of a + * one-off grep that nobody can re-run later. + * + * Running it against the current tree is what makes the audit trustworthy: + * every export in `src/x402-test-fixtures.ts` is still referenced by a live + * test, so there is nothing to delete (see this example's README for the + * evidence table). The tool is kept because "is this fixture still used?" is a + * recurring question, and because a naive grep answers it wrongly. + * + * Why not just grep: + * + * 1. A bare `grep -c NAME` counts the import line itself, so a fixture that + * is imported and never used still looks used. + * 2. It also counts unrelated local declarations that happen to share the + * name — `PAYTO` is defined independently in three other test files, so + * grep reports references that have nothing to do with the fixture. + * 3. It cannot see the difference between an export nobody imports (delete + * the export) and an import nobody uses (delete the import). + * + * This module resolves all three by reading the import statement of each + * consumer, then counting references in that consumer's body with the fixture + * import line excluded. + * + * Deliberately dependency-free (no TypeScript compiler API, no AST library): + * fixture modules are plain `export const` / `export function` declarations and + * are imported with simple named-import statements, so line-oriented parsing is + * sufficient and keeps the example self-contained. See "Limits" in the README + * for the syntax this does not attempt to handle. + */ + +/** A named export declared by the fixture module. */ +export interface FixtureExport { + name: string; + /** `const` for values, `function` for helpers. */ + kind: "const" | "function"; +} + +/** One consumer's use of the fixture module. */ +export interface ConsumerUsage { + /** Path of the importing file, as given to the audit. */ + file: string; + /** Names pulled in by that file's fixture import statement. */ + imported: string[]; + /** Imported names with zero references in the body — dead imports. */ + unusedImports: string[]; + /** Reference count per imported name, excluding the import line. */ + references: Record; +} + +/** The verdict for a single fixture export. */ +export interface FixtureVerdict { + name: string; + kind: "const" | "function"; + /** Total references across every consumer, excluding import lines. */ + totalReferences: number; + /** Consumers that import it (whether or not they use it). */ + importedBy: string[]; + /** + * True when no consumer imports it, or every consumer that imports it never + * references it. These are the exports that are safe to delete. + */ + unused: boolean; +} + +export interface AuditInput { + /** Source of the fixture module (e.g. `src/x402-test-fixtures.ts`). */ + fixtureSource: string; + /** + * Every file that might import the fixture module. Files that don't import + * it are ignored, so it is safe to pass the whole test suite. + */ + consumers: Array<{ file: string; source: string }>; + /** + * Substring identifying the fixture module in an import specifier — e.g. + * `"x402-test-fixtures"`. Matched against the whole import line, so it works + * for both `./x402-test-fixtures` and `../src/x402-test-fixtures`. + */ + moduleId: string; +} + +export interface AuditResult { + exports: FixtureExport[]; + consumers: ConsumerUsage[]; + verdicts: FixtureVerdict[]; + /** Exports safe to delete — none, for the current tree. */ + unusedExports: string[]; + /** Imports safe to delete, as `file` -> names. */ + deadImports: Array<{ file: string; names: string[] }>; +} + +const EXPORT_DECL = /^\s*export\s+(const|function)\s+([A-Za-z_$][\w$]*)/; + +/** Parse the `export const` / `export function` declarations of a fixture module. */ +export function parseFixtureExports(source: string): FixtureExport[] { + const found: FixtureExport[] = []; + const seen = new Set(); + for (const line of source.split("\n")) { + const m = EXPORT_DECL.exec(line); + if (!m) continue; + const [, kind, name] = m; + // A re-declaration can't happen in valid TS, but guard anyway so a + // malformed file produces a clean list rather than duplicates. + if (seen.has(name)) continue; + seen.add(name); + found.push({ name, kind: kind as "const" | "function" }); + } + return found; +} + +/** + * Extract the named bindings of the import statement that pulls in `moduleId`. + * Returns `[]` when the file doesn't import the module at all. + * + * Handles the single-line named-import form these fixtures use, including + * `as` aliases (the *local* name is what the body references, so that is what + * gets counted) and `type` modifiers. + */ +export function parseFixtureImports(source: string, moduleId: string): string[] { + const line = source.split("\n").find((l) => l.includes(moduleId) && /^\s*import\b/.test(l)); + if (!line) return []; + const braces = /{([^}]*)}/.exec(line); + if (!braces) return []; + return braces[1] + .split(",") + .map((part) => { + const cleaned = part.trim().replace(/^type\s+/, ""); + if (!cleaned) return ""; + // `X as Y` — the body refers to Y. + const alias = /\s+as\s+/.test(cleaned) ? cleaned.split(/\s+as\s+/)[1] : cleaned; + return alias.trim(); + }) + .filter(Boolean); +} + +/** + * Count whole-word references to `name` in `source`, ignoring any line that + * imports the fixture module. Excluding that line is the whole point: it is + * what separates "imported and used" from merely "imported". + */ +export function countReferences(source: string, name: string, moduleId: string): number { + const body = source + .split("\n") + .filter((l) => !(l.includes(moduleId) && /^\s*import\b/.test(l))) + .join("\n"); + // Escape regex metacharacters; identifiers shouldn't contain them, but a + // caller may pass an arbitrary string. + const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return (body.match(new RegExp(`\\b${escaped}\\b`, "g")) ?? []).length; +} + +/** + * Audit a fixture module against its consumers. + * + * An export counts as used when at least one consumer imports it AND + * references it outside the import line. Anything else is reported as unused + * and is safe to delete. + */ +export function auditFixtures(input: AuditInput): AuditResult { + const { fixtureSource, consumers, moduleId } = input; + const exports = parseFixtureExports(fixtureSource); + const exportNames = new Set(exports.map((e) => e.name)); + + const usage: ConsumerUsage[] = []; + for (const { file, source } of consumers) { + const imported = parseFixtureImports(source, moduleId); + if (imported.length === 0) continue; // not a consumer + + const references: Record = {}; + const unusedImports: string[] = []; + for (const name of imported) { + const count = countReferences(source, name, moduleId); + references[name] = count; + if (count === 0) unusedImports.push(name); + } + usage.push({ file, imported, unusedImports, references }); + } + + const verdicts: FixtureVerdict[] = exports.map(({ name, kind }) => { + const importedBy = usage.filter((u) => u.imported.includes(name)).map((u) => u.file); + const totalReferences = usage.reduce((sum, u) => sum + (u.references[name] ?? 0), 0); + return { name, kind, totalReferences, importedBy, unused: totalReferences === 0 }; + }); + + return { + exports, + consumers: usage, + verdicts, + unusedExports: verdicts.filter((v) => v.unused).map((v) => v.name), + deadImports: usage + // Only report dead imports of names this fixture module actually exports. + .map((u) => ({ file: u.file, names: u.unusedImports.filter((n) => exportNames.has(n)) })) + .filter((d) => d.names.length > 0), + }; +} + +/** Render an audit as a Markdown table plus a verdict line, for a PR or CI log. */ +export function formatAuditReport(result: AuditResult): string { + const lines: string[] = [ + "| Export | Kind | References | Used by |", + "| --- | --- | ---: | --- |", + ]; + for (const v of result.verdicts) { + const users = v.importedBy.length > 0 ? v.importedBy.join(", ") : "—"; + lines.push(`| \`${v.name}\` | ${v.kind} | ${v.totalReferences} | ${users} |`); + } + + lines.push(""); + lines.push( + result.unusedExports.length === 0 + ? `All ${result.verdicts.length} exports are referenced by at least one consumer — nothing to remove.` + : `Unused exports (safe to remove): ${result.unusedExports.map((n) => `\`${n}\``).join(", ")}`, + ); + + for (const dead of result.deadImports) { + lines.push(`Dead imports in ${dead.file}: ${dead.names.map((n) => `\`${n}\``).join(", ")}`); + } + + return lines.join("\n"); +} diff --git a/contrib/x402-client-fallback.test.ts b/contrib/x402-client-fallback.test.ts index a1c889b..1d31e33 100644 --- a/contrib/x402-client-fallback.test.ts +++ b/contrib/x402-client-fallback.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { createX402ClientWithFallback } from "./x402-client-fallback"; -import { C_ADDRESS, SIM_SOURCE, requirements, response402 } from "../src/x402-test-fixtures"; +import { C_ADDRESS, SIM_SOURCE } from "../src/x402-test-fixtures"; import type { SmartAccountX402Signer } from "../src/index"; const stubSigner: SmartAccountX402Signer = {