feat: finsihed adding utility commands ce843c47
Steve · 2025-08-16 20:05 10 file(s) · +216 −51
biome.json (added) +34 −0
1 +
{
2 +
	"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
3 +
	"vcs": {
4 +
		"enabled": false,
5 +
		"clientKind": "git",
6 +
		"useIgnoreFile": false
7 +
	},
8 +
	"files": {
9 +
		"ignoreUnknown": false
10 +
	},
11 +
	"formatter": {
12 +
		"enabled": true,
13 +
		"indentStyle": "tab"
14 +
	},
15 +
	"linter": {
16 +
		"enabled": true,
17 +
		"rules": {
18 +
			"recommended": true
19 +
		}
20 +
	},
21 +
	"javascript": {
22 +
		"formatter": {
23 +
			"quoteStyle": "double"
24 +
		}
25 +
	},
26 +
	"assist": {
27 +
		"enabled": true,
28 +
		"actions": {
29 +
			"source": {
30 +
				"organizeImports": "on"
31 +
			}
32 +
		}
33 +
	}
34 +
}
package.json +3 −1
7 7
	},
8 8
	"scripts": {
9 9
		"build": "bun build src/index.ts --outdir dist --target node",
10 -
		"dev": "bun run build && bun link"
10 +
		"dev": "bun run build && bun link",
11 +
		"lint": "bunx @biome-js/biome lint --write src",
12 +
		"format": "bunx @biome-js/biome format --write src"
11 13
	},
12 14
	"devDependencies": {
13 15
		"@types/bun": "latest"
src/commands/index.ts +1 −0
1 1
export * from "./profile";
2 2
export * from "./resolve";
3 +
export * from "./utils";
src/commands/profile.ts +13 −22
1 1
import { getRecords } from "@ensdomains/ensjs/public";
2 2
import { getSubgraphRecords } from "@ensdomains/ensjs/subgraph";
3 -
import { type Address, isHex } from "viem";
4 -
import { normalize } from "viem/ens";
3 +
import type { Address } from "viem";
5 4
import colors from "yoctocolors";
6 -
import { spinner } from "../utils/spinner";
7 -
import type { ResolveOptions } from "../utils/types";
8 -
import { ensClient } from "../utils/viem";
5 +
import {
6 +
	ensClient,
7 +
	getNameAndAddress,
8 +
	type ResolveOptions,
9 +
	spinner,
10 +
} from "../utils";
9 11
10 12
function printProfile(
11 13
	name: string | null,
102 104
}
103 105
104 106
export async function profile(options: ResolveOptions) {
105 -
	let name: string | null;
106 -
	let address: Address | null;
107 -
	let input: "address" | "name";
108 -
109 107
	spinner.start();
110 108
111 -
	// Handle name or address
112 -
	if (isHex(options.input)) {
113 -
		address = options.input;
114 -
		input = "address";
115 -
		name = await ensClient.getEnsName({
116 -
			address: input as Address,
117 -
		});
118 -
	} else {
119 -
		name = options.input;
120 -
		input = "name";
121 -
		address = await ensClient.getEnsAddress({
122 -
			name: normalize(options.input as string),
123 -
		});
109 +
	const { name, address } = await getNameAndAddress(options);
110 +
111 +
	if (!name || !address) {
112 +
		spinner.stop();
113 +
		console.log("404: Name not found");
114 +
		return;
124 115
	}
125 116
126 117
	try {
src/commands/resolve.ts +12 −20
1 1
import { getContentHashRecord, getRecords } from "@ensdomains/ensjs/public";
2 -
import { type Address, isHex } from "viem";
3 2
import { normalize } from "viem/ens";
4 -
import type { ResolveOptions } from "../utils/types";
5 -
import { ensClient } from "../utils/viem";
6 -
import { spinner } from "../utils/spinner";
3 +
import {
4 +
	ensClient,
5 +
	getNameAndAddress,
6 +
	type ResolveOptions,
7 +
	spinner,
8 +
} from "../utils";
7 9
8 10
export async function resolve(options: ResolveOptions) {
9 -
	let name: string | null;
10 -
	let address: Address | null;
11 -
	let input: "address" | "name";
12 11
	spinner.start();
13 12
14 -
	// Handle name or address
15 -
	if (isHex(options.input)) {
16 -
		address = options.input;
17 -
		input = "address";
18 -
		name = await ensClient.getEnsName({
19 -
			address: input as Address,
20 -
		});
21 -
	} else {
22 -
		name = options.input;
23 -
		input = "name";
24 -
		address = await ensClient.getEnsAddress({
25 -
			name: normalize(options.input as string),
26 -
		});
13 +
	const { name, address, input } = await getNameAndAddress(options);
14 +
15 +
	if (!name || !address) {
16 +
		spinner.stop();
17 +
		console.log("404: Name not found");
18 +
		return;
27 19
	}
28 20
29 21
	// Handle TXT
src/commands/utils.ts (added) +36 −0
1 +
import { labelhash, namehash } from "viem";
2 +
import { ensClient, spinner } from "../utils";
3 +
import { normalize } from "viem/ens";
4 +
import { addresses } from "@ensdomains/ensjs/contracts";
5 +
6 +
export async function getLabelHash(options: { name: string }) {
7 +
	spinner.start();
8 +
	const res = labelhash(options.name as string);
9 +
	spinner.stop();
10 +
	console.log(res);
11 +
}
12 +
13 +
export async function getNamehash(options: { name: string }) {
14 +
	spinner.start();
15 +
	const res = namehash(options.name as string);
16 +
	spinner.stop();
17 +
	console.log(res);
18 +
}
19 +
20 +
export async function getResolver({ name }: { name: string }) {
21 +
	spinner.start();
22 +
	const resolver = await ensClient.getEnsResolver({
23 +
		name: normalize(name),
24 +
	});
25 +
	spinner.stop();
26 +
	console.log(resolver);
27 +
}
28 +
29 +
export function getDeployments() {
30 +
	for (const [chainId, contracts] of Object.entries(addresses)) {
31 +
		console.log(`Chain ID: ${chainId}`);
32 +
		for (const [contractName, contractData] of Object.entries(contracts)) {
33 +
			console.log(`  ${contractName}: ${contractData.address}`);
34 +
		}
35 +
	}
36 +
}
src/index.ts +77 −3
10 10
	string,
11 11
	subcommands,
12 12
} from "cmd-ts";
13 -
import { profile as profileCmd, resolve as resolveCmd } from "./commands";
13 +
import {
14 +
	getDeployments,
15 +
	getLabelHash,
16 +
	getNamehash,
17 +
	getResolver,
18 +
	profile as profileCmd,
19 +
	resolve as resolveCmd,
20 +
} from "./commands";
14 21
15 22
const resolve = command({
16 23
	name: "resolve",
49 56
});
50 57
51 58
const profile = command({
52 -
	name: "resolve",
59 +
	name: "profile",
53 60
	description: "Resolve an ENS name to an address or vice versa",
54 61
	args: {
55 62
		input: positional({
60 67
	handler: async (args) => {
61 68
		if (!args.input) {
62 69
			console.log(
63 -
				"Please provide an address or ENS Name `atlas resolve <ADDRESS | vitalik.eth>`",
70 +
				"Please provide an address or ENS Name `atlas profile <ADDRESS | vitalik.eth>`",
64 71
			);
65 72
			return;
66 73
		}
68 75
	},
69 76
});
70 77
78 +
const namehash = command({
79 +
	name: "namehash",
80 +
	description: "Get a namehash for an ENS name",
81 +
	args: {
82 +
		name: positional({
83 +
			type: string,
84 +
			description: "ENS name for the namehash",
85 +
		}),
86 +
	},
87 +
	handler: async (args) => {
88 +
		if (!args.name) {
89 +
			console.log("Please provide an ENS Name `atlas namehash <vitalik.eth>`");
90 +
			return;
91 +
		}
92 +
		await getNamehash(args);
93 +
	},
94 +
});
95 +
96 +
const labelhash = command({
97 +
	name: "labelhash",
98 +
	description: "Get a labelhash for an ENS name",
99 +
	args: {
100 +
		name: positional({
101 +
			type: string,
102 +
			description: "ENS for the labelhash",
103 +
		}),
104 +
	},
105 +
	handler: async (args) => {
106 +
		if (!args.name) {
107 +
			console.log("Please provide an ENS Name `atlas labelhash <vitalik.eth>`");
108 +
			return;
109 +
		}
110 +
		await getLabelHash(args);
111 +
	},
112 +
});
113 +
114 +
const resolver = command({
115 +
	name: "resolver",
116 +
	description: "Get the current resolver for an ENS Name",
117 +
	args: {
118 +
		name: positional({
119 +
			type: string,
120 +
			description: "Target ENS name for the resolver query",
121 +
		}),
122 +
	},
123 +
	handler: async (args) => {
124 +
		if (!args.name) {
125 +
			console.log("Please provide an ENS Name `atlas resolver <vitalik.eth>`");
126 +
			return;
127 +
		}
128 +
		await getResolver(args);
129 +
	},
130 +
});
131 +
132 +
const deployments = command({
133 +
	name: "deployments",
134 +
	description: "Print a list of currently deployed ENS contracts",
135 +
	args: {},
136 +
	handler: () => {
137 +
		getDeployments();
138 +
	},
139 +
});
140 +
71 141
const cli = subcommands({
72 142
	name: "atlas",
73 143
	description: "Explore ENS with Atlas",
75 145
	cmds: {
76 146
		profile,
77 147
		resolve,
148 +
		namehash,
149 +
		labelhash,
150 +
		resolver,
151 +
		deployments,
78 152
	},
79 153
});
80 154
src/utils/general.ts (added) +31 −0
1 +
import { isHex, type Address } from "viem";
2 +
import { normalize } from "viem/ens";
3 +
import { ensClient, type ResolveOptions } from "./";
4 +
5 +
export async function getNameAndAddress(options: ResolveOptions) {
6 +
	let name: string | null = null;
7 +
	let address: Address | null = null;
8 +
	let input: "address" | "name";
9 +
10 +
	try {
11 +
		// Handle name or address
12 +
		if (isHex(options.input)) {
13 +
			address = options.input;
14 +
			input = "address";
15 +
			name = await ensClient.getEnsName({
16 +
				address: options.input as Address,
17 +
			});
18 +
		} else {
19 +
			name = options.input;
20 +
			input = "name";
21 +
			address = await ensClient.getEnsAddress({
22 +
				name: normalize(options.input as string),
23 +
			});
24 +
		}
25 +
26 +
		return { name, address, input };
27 +
	} catch (_error) {
28 +
		console.error(`Failed to resolve: ${options.input}`);
29 +
		return { name: null, address: null, input: "name" };
30 +
	}
31 +
}
src/utils/index.ts (added) +4 −0
1 +
export * from "./general";
2 +
export * from "./spinner";
3 +
export * from "./types";
4 +
export * from "./viem";
src/utils/types.ts +5 −5
1 1
import type { Address } from "viem";
2 2
3 3
export type ResolveOptions = {
4 -
  input: string | Address | null;
5 -
  chain?: string;
6 -
  contenthash?: boolean;
7 -
  txt?: string;
8 -
}
4 +
	input: string | Address | null;
5 +
	chain?: string;
6 +
	contenthash?: boolean;
7 +
	txt?: string;
8 +
};