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