diff --git a/.changeset/compose-selectors.md b/.changeset/compose-selectors.md
deleted file mode 100644
index 83444612..00000000
--- a/.changeset/compose-selectors.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-"@perfect-abstractions/compose-cli": patch
----
-
-Add `compose selectors` to list and decode a deployed diamond's selectors by facet.
diff --git a/.changeset/inspect-unknown-signatures.md b/.changeset/inspect-unknown-signatures.md
new file mode 100644
index 00000000..7a7b641d
--- /dev/null
+++ b/.changeset/inspect-unknown-signatures.md
@@ -0,0 +1,5 @@
+---
+"@perfect-abstractions/compose-cli": patch
+---
+
+Mark selectors with unknown signatures in `compose inspect` output.
diff --git a/cli/src/comander.ts b/cli/src/comander.ts
index cdfcd681..899fecd4 100644
--- a/cli/src/comander.ts
+++ b/cli/src/comander.ts
@@ -61,12 +61,6 @@ export function buildProgram(): Command {
.argument("
", "Diamond contract address")
.option("--chain ", "Chain key from compose.json", "local")
- program
- .command("selectors")
- .description("List a deployed diamond's selectors by facet")
- .argument("", "Diamond contract address")
- .option("--chain ", "Chain key from compose.json", "local")
-
return program;
}
@@ -100,7 +94,7 @@ export function parseArgs(argv: string[]): { command: string; flags: Record typeof arg === "string" && arg !== command,
);
if (positionalArgs.length > 0) {
- if ((command === "inspect" || command === "selectors") && !flags.address) {
+ if (command === "inspect" && !flags.address) {
flags.address = positionalArgs[0];
} else if (!flags.projectName) {
flags.projectName = positionalArgs[0];
diff --git a/cli/src/modules/inspect/output.ts b/cli/src/modules/inspect/output.ts
index 95b0ed52..a921c44b 100644
--- a/cli/src/modules/inspect/output.ts
+++ b/cli/src/modules/inspect/output.ts
@@ -1,4 +1,4 @@
-import { cyan, dim, green } from "../../utils/terminal";
+import { cyan, dim, green, yellow } from "../../utils/terminal";
import type { InspectResult } from "./types";
const TREE_BRANCH = "├── ";
@@ -35,7 +35,9 @@ export function showInspect(result: InspectResult): void {
const isLastSelector = j === facet.selectors.length - 1;
const selectorBranch = isLastSelector ? TREE_LAST : TREE_BRANCH;
- console.log(` ${dim(childPrefix + selectorBranch)}${dim(sel.selector)} ${green(sel.signature)}`);
+ const unknown = sel.signature.toLowerCase() === sel.selector.toLowerCase();
+ const signature = unknown ? yellow("Unknown signature") : green(sel.signature);
+ console.log(` ${dim(childPrefix + selectorBranch)}${dim(sel.selector)} ${signature}`);
}
if (!isLast) {
diff --git a/cli/src/modules/pipelineBuilder/module.ts b/cli/src/modules/pipelineBuilder/module.ts
index f065bf81..df1788e9 100644
--- a/cli/src/modules/pipelineBuilder/module.ts
+++ b/cli/src/modules/pipelineBuilder/module.ts
@@ -5,7 +5,6 @@ import { CatalogPipeline } from "../../pipelines/catalogPipeline";
import { BuildPipeline } from "../../pipelines/buildPipeline";
import { RPCPipeline } from "../../pipelines/rpcPipeline";
import { InspectPipeline } from "../../pipelines/inspectPipeline";
-import { SelectorsPipeline } from "../../pipelines/selectorsPipeline";
import { ValidatePipeline } from "../../pipelines/validatePipeline";
/**
@@ -77,13 +76,6 @@ export const PipelineBuilderModule = {
error: null,
};
return InspectPipeline.execute(ctx);
- case "selectors":
- ctx.state.commandSelected = {
- success: true,
- result: { command: ctx.param.command, address: ctx.param.address, chain: ctx.param.chain },
- error: null,
- };
- return SelectorsPipeline.execute(ctx);
default:
ctx.state.commandRouting = {
success: false,
diff --git a/cli/src/modules/selectors/module.ts b/cli/src/modules/selectors/module.ts
deleted file mode 100644
index 211be623..00000000
--- a/cli/src/modules/selectors/module.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { IDiamondAdapter } from "../../adapters/IDiamondAdapter/interface";
-import type { ComposeContext } from "../../context/types";
-import { inspectAddress } from "../inspect/module";
-import { loadDiamondSelectors } from "../inspect/selectorLoader";
-import type { ResolvedChainConfig } from "../../utils/chainConfig";
-import { showSelectors } from "./output";
-
-export const SelectorsModule = {
- async list(ctx: ComposeContext, diamond: IDiamondAdapter, chain: ResolvedChainConfig): Promise {
- const result = await loadDiamondSelectors(inspectAddress(ctx.param.address), diamond, chain);
- ctx.state.selectors = { success: true, result, error: null };
- showSelectors(result);
- return ctx;
- },
-};
diff --git a/cli/src/modules/selectors/output.ts b/cli/src/modules/selectors/output.ts
deleted file mode 100644
index 0c0703b0..00000000
--- a/cli/src/modules/selectors/output.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { cyan, dim, green, yellow } from "../../utils/terminal";
-import type { InspectResult } from "../inspect/types";
-
-/** Prints a selector-first view grouped by facet address. */
-export function showSelectors(result: InspectResult): void {
- console.log(`\n${cyan("Diamond Selectors")}\n`);
- console.log(` Diamond: ${result.diamond}`);
- console.log(` Chain: ${result.chainKey} (${result.chainId})`);
- console.log();
-
- for (const facet of result.facets) {
- console.log(` ${facet.address}`);
- if (facet.selectors.length === 0) {
- console.log(` ${dim("No selectors")}`);
- }
- for (const { selector, signature } of facet.selectors) {
- const unknown = signature.toLowerCase() === selector.toLowerCase();
- console.log(` ${dim(selector)} ${unknown ? yellow("Unknown signature") : green(signature)}`);
- }
- console.log();
- }
- if (result.facets.length === 0) console.log(` ${dim("No facets found")}\n`);
-}
diff --git a/cli/src/pipelines/selectorsPipeline.ts b/cli/src/pipelines/selectorsPipeline.ts
deleted file mode 100644
index c81b162b..00000000
--- a/cli/src/pipelines/selectorsPipeline.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ComposeContext } from "../context/types";
-import { inspectAddress } from "../modules/inspect/module";
-import { SelectorsModule } from "../modules/selectors/module";
-import { DependencyKey } from "../resolver/dependencyKey";
-import { DependencyResolver } from "../resolver/dependencyResolver";
-import { resolveChainConfig } from "../utils/chainConfig";
-
-/** Focused on-chain selector listing pipeline. */
-export const SelectorsPipeline = {
- async execute(ctx: ComposeContext): Promise {
- inspectAddress(ctx.param.address);
- const chainKey = typeof ctx.param.chain === "string" ? ctx.param.chain : "local";
- const chain = await resolveChainConfig({ chainKey });
- const dependencies = await DependencyResolver.resolve([{
- key: DependencyKey.Diamond,
- params: { chainKey: chain.chainKey },
- }]);
- const diamond = dependencies[DependencyKey.Diamond];
- if (!diamond) throw new Error("Diamond dependency was not resolved");
- return SelectorsModule.list(ctx, diamond, chain);
- },
-};
diff --git a/cli/test/comander.test.ts b/cli/test/comander.test.ts
index facd8af8..669259db 100644
--- a/cli/test/comander.test.ts
+++ b/cli/test/comander.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { parseArgs } from "../src/comander";
+import { buildProgram, parseArgs } from "../src/comander";
describe("validate command arguments", () => {
it("accepts a Compose project root", () => {
@@ -68,6 +68,12 @@ describe("rpc command", () => {
});
describe("inspect command", () => {
+ it("is the only facet and selector listing command", () => {
+ const commands = buildProgram().commands.map((command) => command.name());
+ expect(commands).toContain("inspect");
+ expect(commands).not.toContain("selectors");
+ });
+
it("parses address positional and chain flag", () => {
const result = parseArgs([
"node",
@@ -103,21 +109,4 @@ describe("inspect command", () => {
},
});
});
-
-});
-
-describe("selectors command", () => {
- it("parses address and chain", () => {
- expect(parseArgs(["node", "compose", "selectors", "0x0000000000000000000000000000000000000001", "--chain", "sepolia"])).toEqual({
- command: "selectors",
- flags: { address: "0x0000000000000000000000000000000000000001", chain: "sepolia" },
- });
- });
-
- it("defaults to local", () => {
- expect(parseArgs(["node", "compose", "selectors", "0x0000000000000000000000000000000000000001"])).toEqual({
- command: "selectors",
- flags: { address: "0x0000000000000000000000000000000000000001", chain: "local" },
- });
- });
});
diff --git a/cli/test/modules/selectors/output.test.ts b/cli/test/modules/inspect/output.test.ts
similarity index 74%
rename from cli/test/modules/selectors/output.test.ts
rename to cli/test/modules/inspect/output.test.ts
index 6b797141..ca1aedbf 100644
--- a/cli/test/modules/selectors/output.test.ts
+++ b/cli/test/modules/inspect/output.test.ts
@@ -1,11 +1,11 @@
import { describe, expect, it, vi } from "vitest";
-import { showSelectors } from "../../../src/modules/selectors/output";
+import { showInspect } from "../../../src/modules/inspect/output";
-describe("showSelectors", () => {
- it("groups signatures by facet and marks unknown selectors", () => {
+describe("showInspect", () => {
+ it("shows known signatures and marks unknown selectors", () => {
const log = vi.spyOn(console, "log").mockImplementation(() => {});
try {
- showSelectors({
+ showInspect({
diamond: "0x0000000000000000000000000000000000000001",
chainKey: "local",
chainId: 31337,
@@ -18,10 +18,13 @@ describe("showSelectors", () => {
],
}],
});
+
const output = log.mock.calls.map(([line]) => line).join("\n");
+ expect(output).toContain("1 facets");
+ expect(output).toContain("2 selectors");
expect(output).toContain("facets()");
- expect(output).toContain("Unknown signature");
expect(output).toContain("0xdeadbeef");
+ expect(output).toContain("Unknown signature");
} finally {
log.mockRestore();
}
diff --git a/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts b/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts
index 72ed2dd4..5a957103 100644
--- a/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts
+++ b/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts
@@ -2,7 +2,6 @@ import { describe, expect, it, vi } from "vitest";
import { Context } from "../../../src/context/context";
import { PipelineBuilderModule } from "../../../src/modules/pipelineBuilder/module";
import { ValidatePipeline } from "../../../src/pipelines/validatePipeline";
-import { SelectorsPipeline } from "../../../src/pipelines/selectorsPipeline";
describe("PipelineBuilderModule", () => {
it("routes the validate command to ValidatePipeline", async () => {
@@ -20,19 +19,4 @@ describe("PipelineBuilderModule", () => {
execute.mockRestore();
}
});
-
- it("routes the selectors command to SelectorsPipeline", async () => {
- const ctx = Context.create();
- ctx.param = { command: "selectors", address: "0x0000000000000000000000000000000000000001", chain: "local" };
- const execute = vi.spyOn(SelectorsPipeline, "execute").mockResolvedValue(ctx);
-
- try {
- const result = await PipelineBuilderModule.route(ctx);
- expect(result).toBe(ctx);
- expect(execute).toHaveBeenCalledWith(ctx);
- expect(ctx.state.commandSelected).toMatchObject({ success: true, result: ctx.param });
- } finally {
- execute.mockRestore();
- }
- });
});
diff --git a/cli/test/pipelines/selectorsPipeline/selectorsPipeline.test.ts b/cli/test/pipelines/selectorsPipeline/selectorsPipeline.test.ts
deleted file mode 100644
index dd202064..00000000
--- a/cli/test/pipelines/selectorsPipeline/selectorsPipeline.test.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { describe, expect, it, vi } from "vitest";
-import { Context } from "../../../src/context/context";
-import { DependencyKey } from "../../../src/resolver/dependencyKey";
-
-const mocks = vi.hoisted(() => ({
- resolveChainConfig: vi.fn(),
- resolve: vi.fn(),
- showSelectors: vi.fn(),
-}));
-
-vi.mock("../../../src/utils/chainConfig", () => ({ resolveChainConfig: mocks.resolveChainConfig }));
-vi.mock("../../../src/resolver/dependencyResolver", () => ({ DependencyResolver: { resolve: mocks.resolve } }));
-vi.mock("../../../src/modules/selectors/output", () => ({ showSelectors: mocks.showSelectors }));
-
-import { SelectorsPipeline } from "../../../src/pipelines/selectorsPipeline";
-
-const address = "0x0000000000000000000000000000000000000001";
-
-describe("SelectorsPipeline", () => {
- it("rejects an invalid address before resolving the chain", async () => {
- const ctx = Context.create();
- ctx.param = { address: "invalid" };
- await expect(SelectorsPipeline.execute(ctx)).rejects.toMatchObject({ code: "RPC_INVALID_ADDRESS" });
- expect(mocks.resolveChainConfig).not.toHaveBeenCalled();
- });
-
- it("propagates an unknown chain error before resolving the adapter", async () => {
- mocks.resolveChainConfig.mockRejectedValueOnce(new Error("Unknown chain: missing"));
- const ctx = Context.create();
- ctx.param = { address, chain: "missing" };
-
- await expect(SelectorsPipeline.execute(ctx)).rejects.toThrow("Unknown chain: missing");
- expect(mocks.resolve).not.toHaveBeenCalled();
- });
-
- it("queries facets and decodes selectors", async () => {
- const facets = vi.fn().mockResolvedValue([
- { facet: "0x0000000000000000000000000000000000000002", functionSelectors: ["0x7a0ed627", "0xdeadbeef"] },
- ]);
- mocks.resolveChainConfig.mockResolvedValue({ chainKey: "sepolia", rpcUrl: "https://rpc.example", chainId: 11155111 });
- mocks.resolve.mockResolvedValue({ [DependencyKey.Diamond]: { facets } });
-
- const ctx = Context.create();
- ctx.param = { address, chain: "sepolia" };
- const result = await SelectorsPipeline.execute(ctx);
-
- expect(mocks.resolve).toHaveBeenCalledWith([{ key: DependencyKey.Diamond, params: { chainKey: "sepolia" } }]);
- expect(facets).toHaveBeenCalledWith(address);
- expect(result.state.selectors).toMatchObject({
- success: true,
- result: { facets: [{ selectors: [
- { selector: "0x7a0ed627", signature: "facets()" },
- { selector: "0xdeadbeef", signature: "0xdeadbeef" },
- ] }] },
- });
- expect(mocks.showSelectors).toHaveBeenCalledOnce();
- });
-
- it("propagates RPC failures", async () => {
- mocks.resolveChainConfig.mockResolvedValue({ chainKey: "local", rpcUrl: "http://localhost", chainId: 31337 });
- mocks.resolve.mockResolvedValue({ [DependencyKey.Diamond]: { facets: vi.fn().mockRejectedValue(new Error("RPC unavailable")) } });
- const ctx = Context.create();
- ctx.param = { address };
- await expect(SelectorsPipeline.execute(ctx)).rejects.toThrow("RPC unavailable");
- });
-});