src/index.ts 8.5 K raw
1
#!/usr/bin/env node --no-deprecation
2
import {
3
	binary,
4
	command,
5
	flag,
6
	option,
7
	optional,
8
	positional,
9
	run,
10
	string,
11
	subcommands,
12
} from "cmd-ts";
13
import {
14
	getDeployments,
15
	getLabelHash,
16
	getNamehash,
17
	getResolver,
18
	profile as profileCmd,
19
	resolve as resolveCmd,
20
	setTxt as setTxtCmd,
21
	setAddress as setAddressCmd,
22
	setResolver as setResolverCmd,
23
	setPrimaryName as setPrimaryNameCmd,
24
	setAbi as setAbiCmd,
25
} from "./commands";
26
27
const resolve = command({
28
	name: "resolve",
29
	description: "Resolve an ENS name to an address or vice versa",
30
	args: {
31
		input: positional({
32
			type: string,
33
			description: "Provide either an address or an ENS name to resolve it",
34
		}),
35
		txt: option({
36
			type: optional(string),
37
			description: "Query a specific TXT record for the ENS or Address",
38
			long: "txt",
39
			short: "t",
40
		}),
41
		contenthash: flag({
42
			long: "contenthash",
43
			short: "c",
44
			description: "Fetch the content hash for an ENS name or address",
45
		}),
46
		chain: option({
47
			type: optional(string),
48
			long: "chain",
49
			description: "Get address for a specific chain",
50
		}),
51
		resolverAddress: option({
52
			type: optional(string),
53
			long: "resolver",
54
			short: "r",
55
			description: "Specify a custom resolver address",
56
		}),
57
	},
58
	handler: async (args) => {
59
		if (!args.input) {
60
			console.log(
61
				"Please provide an address or ENS Name `atlas resolve <ADDRESS | vitalik.eth>`",
62
			);
63
			return;
64
		}
65
		await resolveCmd(args);
66
	},
67
});
68
69
const profile = command({
70
	name: "profile",
71
	description: "Resolve an ENS name to an address or vice versa",
72
	args: {
73
		input: positional({
74
			type: string,
75
			description: "Provide either an address or an ENS name to resolve it",
76
		}),
77
		resolverAddress: option({
78
			type: optional(string),
79
			long: "resolver",
80
			short: "r",
81
			description: "Specify a custom resolver address",
82
		}),
83
	},
84
	handler: async (args) => {
85
		if (!args.input) {
86
			console.log(
87
				"Please provide an address or ENS Name `atlas profile <ADDRESS | vitalik.eth>`",
88
			);
89
			return;
90
		}
91
		await profileCmd(args);
92
	},
93
});
94
95
const namehash = command({
96
	name: "namehash",
97
	description: "Get a namehash for an ENS name",
98
	args: {
99
		name: positional({
100
			type: string,
101
			description: "ENS name for the namehash",
102
		}),
103
	},
104
	handler: async (args) => {
105
		if (!args.name) {
106
			console.log("Please provide an ENS Name `atlas namehash <vitalik.eth>`");
107
			return;
108
		}
109
		await getNamehash(args);
110
	},
111
});
112
113
const labelhash = command({
114
	name: "labelhash",
115
	description: "Get a labelhash for an ENS name",
116
	args: {
117
		name: positional({
118
			type: string,
119
			description: "ENS for the labelhash",
120
		}),
121
	},
122
	handler: async (args) => {
123
		if (!args.name) {
124
			console.log("Please provide an ENS Name `atlas labelhash <vitalik.eth>`");
125
			return;
126
		}
127
		await getLabelHash(args);
128
	},
129
});
130
131
const resolver = command({
132
	name: "resolver",
133
	description: "Get the current resolver for an ENS Name",
134
	args: {
135
		name: positional({
136
			type: string,
137
			description: "Target ENS name for the resolver query",
138
		}),
139
	},
140
	handler: async (args) => {
141
		if (!args.name) {
142
			console.log("Please provide an ENS Name `atlas resolver <vitalik.eth>`");
143
			return;
144
		}
145
		await getResolver(args);
146
	},
147
});
148
149
const deployments = command({
150
	name: "deployments",
151
	description: "Print a list of currently deployed ENS contracts",
152
	args: {},
153
	handler: () => {
154
		getDeployments();
155
	},
156
});
157
158
const editTxt = command({
159
	name: "txt",
160
	description:
161
		"Set TXT record for an ENS name (use 'null' to clear the record)",
162
	args: {
163
		name: positional({
164
			type: string,
165
			description: "Target ENS name",
166
		}),
167
		record: positional({
168
			type: string,
169
			description: "The type of TXT you want to update, e.g. com.discord",
170
		}),
171
		value: positional({
172
			type: string,
173
			description:
174
				"Value of the TXT record being set (use 'null' to clear), e.g. myusername",
175
		}),
176
		resolverAddress: option({
177
			type: optional(string),
178
			long: "resolver",
179
			short: "r",
180
			description:
181
				"Resolver address (optional, will auto-detect if not provided)",
182
		}),
183
	},
184
	handler: async (args) => {
185
		if (!args.name || !args.record || args.value === undefined) {
186
			console.log(
187
				"Please provide all required arguments: `atlas edit txt <name> <record> <value>`",
188
			);
189
			return;
190
		}
191
		await setTxtCmd({
192
			...args,
193
			value: args.value === "null" ? "" : args.value,
194
		});
195
	},
196
});
197
198
const editAddress = command({
199
	name: "address",
200
	description: "Set address record for a specific coin/chain",
201
	args: {
202
		name: positional({
203
			type: string,
204
			description: "Target ENS name",
205
		}),
206
		coin: positional({
207
			type: string,
208
			description: "Coin/chain identifier (e.g. ETH, BTC, SOL)",
209
		}),
210
		value: positional({
211
			type: string,
212
			description: "Address value to set",
213
		}),
214
		resolverAddress: option({
215
			type: optional(string),
216
			long: "resolver",
217
			short: "r",
218
			description:
219
				"Resolver address (optional, will auto-detect if not provided)",
220
		}),
221
	},
222
	handler: async (args) => {
223
		if (!args.name || !args.coin || !args.value) {
224
			console.log(
225
				"Please provide all required arguments: `atlas edit address <name> <coin> <value>`",
226
			);
227
			return;
228
		}
229
		await setAddressCmd(args);
230
	},
231
});
232
233
const editResolver = command({
234
	name: "resolver",
235
	description: "Set the resolver for an ENS name",
236
	args: {
237
		name: positional({
238
			type: string,
239
			description: "Target ENS name",
240
		}),
241
		resolverAddress: positional({
242
			type: string,
243
			description: "New resolver address",
244
		}),
245
		contract: option({
246
			type: optional(string),
247
			long: "contract",
248
			short: "c",
249
			description:
250
				"Contract to use: registry or nameWrapper (default: registry)",
251
		}),
252
	},
253
	handler: async (args) => {
254
		if (!args.name || !args.resolverAddress) {
255
			console.log(
256
				"Please provide all required arguments: `atlas edit resolver <name> <resolverAddress>`",
257
			);
258
			return;
259
		}
260
		await setResolverCmd({
261
			...args,
262
			contract: (args.contract as "registry" | "nameWrapper") || "registry",
263
		});
264
	},
265
});
266
267
const editPrimaryName = command({
268
	name: "primary",
269
	description: "Set the primary ENS name for your address",
270
	args: {
271
		name: positional({
272
			type: string,
273
			description: "ENS name to set as primary",
274
		}),
275
	},
276
	handler: async (args) => {
277
		if (!args.name) {
278
			console.log(
279
				"Please provide an ENS name: `atlas edit primary <vitalik.eth>`",
280
			);
281
			return;
282
		}
283
		await setPrimaryNameCmd(args);
284
	},
285
});
286
287
const editAbi = command({
288
	name: "abi",
289
	description:
290
		"Set ABI record for an ENS name (use 'null' to clear the record)",
291
	args: {
292
		name: positional({
293
			type: string,
294
			description: "Target ENS name",
295
		}),
296
		abiPath: positional({
297
			type: string,
298
			description: "Path to ABI JSON file (use 'null' to clear)",
299
		}),
300
		encodeAs: option({
301
			type: optional(string),
302
			long: "encode",
303
			short: "e",
304
			description: "Encoding format: json, zlib, cbor, or uri (default: json)",
305
		}),
306
		resolverAddress: option({
307
			type: optional(string),
308
			long: "resolver",
309
			short: "r",
310
			description:
311
				"Resolver address (optional, will auto-detect if not provided)",
312
		}),
313
	},
314
	handler: async (args) => {
315
		if (!args.name || args.abiPath === undefined) {
316
			console.log(
317
				"Please provide all required arguments: `atlas edit abi <name> <abiPath>`",
318
			);
319
			return;
320
		}
321
		await setAbiCmd({
322
			...args,
323
			abiPath: args.abiPath,
324
			encodeAs: (args.encodeAs as "json" | "zlib" | "cbor" | "uri") || "json",
325
		});
326
	},
327
});
328
329
const edit = subcommands({
330
	name: "edit",
331
	description: "Edit records for an ENS name",
332
	cmds: {
333
		txt: editTxt,
334
		address: editAddress,
335
		resolver: editResolver,
336
		primaryName: editPrimaryName,
337
		abi: editAbi,
338
	},
339
});
340
341
const cli = subcommands({
342
	name: "atlas",
343
	description: `
344
345
        ++   ++
346
     +++  +++  +++             ##       ##########    ##              ##         #####
347
   +++++ +++++  ++++          ####          ##        ##             ###       ##     ##
348
  +++++ +++++++ +++++         ## ##         ##        ##            ## ##      ##
349
  +++++ +++++++ +++++        ##  ##         ##        ##            ##  ##     #####
350
  +++++ +++++++ +++++        #    ##        ##        ##           ##   ##          ####
351
  +++++ +++++++ +++++       #########       ##        ##          #########            ##
352
   +++++ +++++  ++++       ##      ##       ##        ##          ##      ##   ##     ##
353
     +++  +++  +++         ##       ##      ##        #########  ##       ##     ######
354
         ++  +
355
356
357
358
         A CLI for exploring ENS
359
         https://github.com/stevedylandev/atlas
360
361
	`,
362
	version: "0.1.0",
363
	cmds: {
364
		profile,
365
		resolve,
366
		namehash,
367
		labelhash,
368
		resolver,
369
		deployments,
370
		edit,
371
	},
372
});
373
374
async function main() {
375
	try {
376
		await run(binary(cli), process.argv);
377
	} catch (error) {
378
		console.error("Error:", error);
379
		process.exit(1);
380
	}
381
}
382
383
main();