ff1-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ /**
3
+ * Address Validator
4
+ * Validates Ethereum and Tezos wallet addresses
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.validateEthereumAddress = validateEthereumAddress;
8
+ exports.validateTezosAddress = validateTezosAddress;
9
+ exports.validateAddresses = validateAddresses;
10
+ const viem_1 = require("viem");
11
+ /**
12
+ * Validate Ethereum address format
13
+ * Uses viem library for EIP-55 checksum validation
14
+ *
15
+ * @param {string} address - Address to validate
16
+ * @returns {Object} Validation result
17
+ * @returns {boolean} returns.valid - Whether address is valid Ethereum format
18
+ * @returns {string} [returns.error] - Error message if invalid
19
+ * @returns {string} [returns.normalized] - Checksummed address if valid
20
+ * @example
21
+ * const result = validateEthereumAddress('0x1234567890123456789012345678901234567890');
22
+ * if (result.valid) console.log(result.normalized);
23
+ */
24
+ function validateEthereumAddress(address) {
25
+ if (!address || typeof address !== 'string') {
26
+ return {
27
+ valid: false,
28
+ error: 'Address must be a non-empty string',
29
+ };
30
+ }
31
+ try {
32
+ // viem's isAddress checks format and returns true/false
33
+ if (!(0, viem_1.isAddress)(address)) {
34
+ return {
35
+ valid: false,
36
+ error: 'Invalid Ethereum address format. Must be 0x followed by 40 hex characters',
37
+ };
38
+ }
39
+ // getAddress returns the checksummed address
40
+ const normalized = (0, viem_1.getAddress)(address);
41
+ return {
42
+ valid: true,
43
+ normalized,
44
+ };
45
+ }
46
+ catch (err) {
47
+ return {
48
+ valid: false,
49
+ error: `Address validation failed: ${err instanceof Error ? err.message : String(err)}`,
50
+ };
51
+ }
52
+ }
53
+ /**
54
+ * Validate Tezos address format
55
+ * Checks tz1, tz2, tz3, and KT1 address prefixes
56
+ *
57
+ * @param {string} address - Address to validate
58
+ * @returns {Object} Validation result
59
+ * @returns {boolean} returns.valid - Whether address is valid Tezos format
60
+ * @returns {string} [returns.error] - Error message if invalid
61
+ * @returns {string} [returns.type] - Address type (user, contract)
62
+ * @example
63
+ * const result = validateTezosAddress('tz1VSUr8wwNhLAzempoch5d6hLKEUNvD14');
64
+ * if (result.valid) console.log(result.type);
65
+ */
66
+ function validateTezosAddress(address) {
67
+ if (!address || typeof address !== 'string') {
68
+ return {
69
+ valid: false,
70
+ error: 'Address must be a non-empty string',
71
+ };
72
+ }
73
+ // Tezos addresses use base58 encoding with specific prefixes
74
+ // tz1, tz2, tz3: user/implicit accounts (34 chars total, or longer with suffix)
75
+ // KT1: contracts (34 chars total, or longer with suffix)
76
+ // Base58 alphabet: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
77
+ const userAddressRegex = /^tz[1-3][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{30,}$/;
78
+ const contractAddressRegex = /^KT1[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{30,}$/;
79
+ if (userAddressRegex.test(address)) {
80
+ return {
81
+ valid: true,
82
+ type: 'user',
83
+ };
84
+ }
85
+ if (contractAddressRegex.test(address)) {
86
+ return {
87
+ valid: true,
88
+ type: 'contract',
89
+ };
90
+ }
91
+ return {
92
+ valid: false,
93
+ error: 'Invalid Tezos address format. Must start with tz1/tz2/tz3 (user) or KT1 (contract)',
94
+ };
95
+ }
96
+ /**
97
+ * Validate mixed Ethereum and Tezos addresses, ENS domains, and Tezos domains
98
+ * Detects address type and applies appropriate validation
99
+ *
100
+ * Supported formats:
101
+ * - Ethereum addresses: 0x followed by 40 hex characters
102
+ * - Tezos addresses: tz1/tz2/tz3 (user) or KT1 (contract)
103
+ * - ENS domains: alphanumeric names ending in .eth
104
+ * - Tezos domains: alphanumeric names ending in .tez
105
+ *
106
+ * @param {Array<string>} addresses - Array of addresses to validate
107
+ * @returns {Object} Validation result
108
+ * @returns {boolean} returns.valid - Whether all addresses are valid
109
+ * @returns {Array<Object>} returns.results - Validation result for each address
110
+ * @returns {Array<string>} returns.errors - List of error messages
111
+ * @example
112
+ * const result = validateAddresses(['0x...', 'tz1...', 'reas.eth', 'einstein-rosen.tez']);
113
+ * if (!result.valid) console.log(result.errors);
114
+ */
115
+ function validateAddresses(addresses) {
116
+ const results = [];
117
+ const errors = [];
118
+ if (!Array.isArray(addresses)) {
119
+ errors.push('Input must be an array of addresses');
120
+ return { valid: false, results, errors };
121
+ }
122
+ if (addresses.length === 0) {
123
+ errors.push('At least one address is required for validation');
124
+ return { valid: false, results, errors };
125
+ }
126
+ for (const address of addresses) {
127
+ if (typeof address !== 'string') {
128
+ errors.push(`Invalid input: ${JSON.stringify(address)} is not a string`);
129
+ results.push({
130
+ address: String(address),
131
+ valid: false,
132
+ type: 'unknown',
133
+ error: 'Address must be a string',
134
+ });
135
+ continue;
136
+ }
137
+ const trimmed = address.trim();
138
+ // Try Ethereum first (starts with 0x)
139
+ if (trimmed.startsWith('0x')) {
140
+ const ethResult = validateEthereumAddress(trimmed);
141
+ if (ethResult.valid) {
142
+ results.push({
143
+ address: trimmed,
144
+ valid: true,
145
+ type: 'ethereum',
146
+ normalized: ethResult.normalized,
147
+ });
148
+ }
149
+ else {
150
+ const errorMsg = `Invalid Ethereum address "${trimmed}": ${ethResult.error}`;
151
+ errors.push(errorMsg);
152
+ results.push({
153
+ address: trimmed,
154
+ valid: false,
155
+ type: 'ethereum',
156
+ error: ethResult.error,
157
+ });
158
+ }
159
+ }
160
+ else if (trimmed.startsWith('tz') || trimmed.startsWith('KT1')) {
161
+ // Try Tezos
162
+ const tezResult = validateTezosAddress(trimmed);
163
+ if (tezResult.valid) {
164
+ results.push({
165
+ address: trimmed,
166
+ valid: true,
167
+ type: tezResult.type || 'tezos',
168
+ });
169
+ }
170
+ else {
171
+ const errorMsg = `Invalid Tezos address "${trimmed}": ${tezResult.error}`;
172
+ errors.push(errorMsg);
173
+ results.push({
174
+ address: trimmed,
175
+ valid: false,
176
+ type: 'tezos',
177
+ error: tezResult.error,
178
+ });
179
+ }
180
+ }
181
+ else if (trimmed.endsWith('.eth')) {
182
+ // ENS domain name (Ethereum Name Service)
183
+ // These are valid owner identifiers that will be resolved to addresses
184
+ const ensPattern = /^[a-z0-9-]+\.eth$/i;
185
+ if (ensPattern.test(trimmed)) {
186
+ results.push({
187
+ address: trimmed,
188
+ valid: true,
189
+ type: 'ens',
190
+ });
191
+ }
192
+ else {
193
+ const errorMsg = `Invalid ENS domain "${trimmed}". Must be alphanumeric with hyphens ending in .eth`;
194
+ errors.push(errorMsg);
195
+ results.push({
196
+ address: trimmed,
197
+ valid: false,
198
+ type: 'ens',
199
+ error: 'Invalid ENS domain format',
200
+ });
201
+ }
202
+ }
203
+ else if (trimmed.endsWith('.tez')) {
204
+ // Tezos domain name
205
+ // These are valid owner identifiers that will be resolved to addresses
206
+ const tezDomainPattern = /^[a-z0-9-]+\.tez$/i;
207
+ if (tezDomainPattern.test(trimmed)) {
208
+ results.push({
209
+ address: trimmed,
210
+ valid: true,
211
+ type: 'tezos-domain',
212
+ });
213
+ }
214
+ else {
215
+ const errorMsg = `Invalid Tezos domain "${trimmed}". Must be alphanumeric with hyphens ending in .tez`;
216
+ errors.push(errorMsg);
217
+ results.push({
218
+ address: trimmed,
219
+ valid: false,
220
+ type: 'tezos-domain',
221
+ error: 'Invalid Tezos domain format',
222
+ });
223
+ }
224
+ }
225
+ else {
226
+ const errorMsg = `Unknown address format "${trimmed}". Must be 0x... (Ethereum), tz/KT1 (Tezos), .eth (ENS), or .tez (Tezos domain)`;
227
+ errors.push(errorMsg);
228
+ results.push({
229
+ address: trimmed,
230
+ valid: false,
231
+ type: 'unknown',
232
+ error: 'Must be Ethereum (0x...), Tezos (tz1/tz2/tz3/KT1), ENS (.eth), or Tezos domain (.tez)',
233
+ });
234
+ }
235
+ }
236
+ const allValid = results.every((r) => r.valid);
237
+ return {
238
+ valid: allValid,
239
+ results,
240
+ errors,
241
+ };
242
+ }
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ /**
3
+ * Domain Resolution Utilities
4
+ * Resolves blockchain domain names (ENS, TNS) to their corresponding addresses
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.resolveDomain = resolveDomain;
44
+ exports.resolveDomainsBatch = resolveDomainsBatch;
45
+ exports.displayResolutionResults = displayResolutionResults;
46
+ const viem_1 = require("viem");
47
+ const chains_1 = require("viem/chains");
48
+ const ens_1 = require("viem/ens");
49
+ const axios_1 = __importDefault(require("axios"));
50
+ const chalk_1 = __importDefault(require("chalk"));
51
+ const logger = __importStar(require("../logger"));
52
+ /**
53
+ * ENS resolver using viem
54
+ */
55
+ class ENSResolver {
56
+ client;
57
+ constructor() {
58
+ this.client = (0, viem_1.createPublicClient)({
59
+ chain: chains_1.mainnet,
60
+ transport: (0, viem_1.http)(),
61
+ });
62
+ }
63
+ /**
64
+ * Resolve an ENS domain to its Ethereum address
65
+ *
66
+ * @param {string} domain - ENS domain (e.g., 'vitalik.eth')
67
+ * @returns {Promise<string|null>} Resolved address or null
68
+ */
69
+ async resolve(domain) {
70
+ try {
71
+ const address = await this.client.getEnsAddress({
72
+ name: (0, ens_1.normalize)(domain),
73
+ });
74
+ return address;
75
+ }
76
+ catch (error) {
77
+ logger.debug(`ENS resolution failed for ${domain}: ${error}`);
78
+ return null;
79
+ }
80
+ }
81
+ }
82
+ /**
83
+ * TNS (Tezos Name Service) resolver using Tezos Domains GraphQL API
84
+ *
85
+ * Uses the official Tezos Domains API for reliable resolution
86
+ * API: https://api.tezos.domains/graphql
87
+ */
88
+ class TNSResolver {
89
+ apiUrl;
90
+ constructor() {
91
+ this.apiUrl = 'https://api.tezos.domains/graphql';
92
+ }
93
+ /**
94
+ * Resolve a TNS domain to its Tezos address using GraphQL API
95
+ *
96
+ * @param {string} domain - TNS domain (e.g., 'alice.tez', 'einstein-rosen.tez')
97
+ * @returns {Promise<string|null>} Resolved address or null
98
+ */
99
+ async resolve(domain) {
100
+ try {
101
+ // GraphQL query to resolve domain (using GET request)
102
+ // Note: API only supports 'address' field, not 'expiry'
103
+ const query = `{ domain(name: "${domain}") { address } }`;
104
+ const response = await axios_1.default.get(this.apiUrl, {
105
+ params: { query },
106
+ timeout: 10000,
107
+ });
108
+ if (response.data?.errors) {
109
+ logger.debug(`TNS API returned errors for ${domain}: ${JSON.stringify(response.data.errors)}`);
110
+ return null;
111
+ }
112
+ const domainData = response.data?.data?.domain;
113
+ if (!domainData || !domainData.address) {
114
+ logger.debug(`TNS domain ${domain} not found - domainData: ${JSON.stringify(domainData)}`);
115
+ return null;
116
+ }
117
+ logger.debug(`TNS resolved ${domain} → ${domainData.address}`);
118
+ return domainData.address;
119
+ }
120
+ catch (error) {
121
+ const errorMessage = error instanceof Error ? error.message : String(error);
122
+ logger.debug(`TNS resolution failed for ${domain}: ${errorMessage}`);
123
+ return null;
124
+ }
125
+ }
126
+ }
127
+ /**
128
+ * Determine domain type based on TLD
129
+ *
130
+ * @param {string} domain - Domain name
131
+ * @returns {string|null} Domain type ('ens', 'tns') or null
132
+ */
133
+ function getDomainType(domain) {
134
+ const normalizedDomain = domain.toLowerCase();
135
+ if (normalizedDomain.endsWith('.eth')) {
136
+ return 'ens';
137
+ }
138
+ else if (normalizedDomain.endsWith('.tez')) {
139
+ return 'tns';
140
+ }
141
+ return null;
142
+ }
143
+ /**
144
+ * Validate domain name format
145
+ *
146
+ * @param {string} domain - Domain to validate
147
+ * @returns {boolean} Whether domain is valid
148
+ */
149
+ function isValidDomain(domain) {
150
+ if (!domain || typeof domain !== 'string') {
151
+ return false;
152
+ }
153
+ const trimmedDomain = domain.trim();
154
+ if (trimmedDomain.length === 0) {
155
+ return false;
156
+ }
157
+ const domainType = getDomainType(trimmedDomain);
158
+ return domainType !== null;
159
+ }
160
+ /**
161
+ * Resolve a single domain to its blockchain address
162
+ *
163
+ * @param {string} domain - Domain name to resolve (e.g., 'vitalik.eth', 'alice.tez')
164
+ * @returns {Promise<DomainResolution>} Resolution result
165
+ * @example
166
+ * const result = await resolveDomain('vitalik.eth');
167
+ * if (result.resolved) {
168
+ * console.log(`${result.domain} -> ${result.address}`);
169
+ * }
170
+ */
171
+ async function resolveDomain(domain) {
172
+ const trimmedDomain = domain.trim();
173
+ // Validate domain
174
+ if (!isValidDomain(trimmedDomain)) {
175
+ return {
176
+ domain: trimmedDomain,
177
+ address: null,
178
+ resolved: false,
179
+ error: `Invalid or unsupported domain: ${trimmedDomain}`,
180
+ };
181
+ }
182
+ const domainType = getDomainType(trimmedDomain);
183
+ try {
184
+ let address = null;
185
+ if (domainType === 'ens') {
186
+ const ensResolver = new ENSResolver();
187
+ address = await ensResolver.resolve(trimmedDomain);
188
+ }
189
+ else if (domainType === 'tns') {
190
+ const tnsResolver = new TNSResolver();
191
+ address = await tnsResolver.resolve(trimmedDomain);
192
+ }
193
+ if (!address) {
194
+ return {
195
+ domain: trimmedDomain,
196
+ address: null,
197
+ resolved: false,
198
+ error: `Could not resolve ${trimmedDomain}`,
199
+ };
200
+ }
201
+ return {
202
+ domain: trimmedDomain,
203
+ address,
204
+ resolved: true,
205
+ };
206
+ }
207
+ catch (error) {
208
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error during resolution';
209
+ logger.debug(`Domain resolution error for ${trimmedDomain}: ${errorMessage}`);
210
+ return {
211
+ domain: trimmedDomain,
212
+ address: null,
213
+ resolved: false,
214
+ error: errorMessage,
215
+ };
216
+ }
217
+ }
218
+ /**
219
+ * Resolve multiple domains in batch (concurrent processing)
220
+ *
221
+ * Supports ENS (.eth) and TNS (.tez) domains.
222
+ * Processes all domains concurrently for optimal performance.
223
+ *
224
+ * @param {string[]} domains - Array of domain names to resolve
225
+ * @returns {Promise<BatchResolutionResult>} Batch resolution result with domain->address map
226
+ * @example
227
+ * const result = await resolveDomainsBatch(['vitalik.eth', 'alice.tez']);
228
+ * if (result.success) {
229
+ * console.log(result.domainMap); // { 'vitalik.eth': '0x...', 'alice.tez': 'tz...' }
230
+ * }
231
+ */
232
+ async function resolveDomainsBatch(domains) {
233
+ // Validate input
234
+ if (!Array.isArray(domains) || domains.length === 0) {
235
+ return {
236
+ success: false,
237
+ resolutions: [],
238
+ domainMap: {},
239
+ errors: ['No domains provided for resolution'],
240
+ };
241
+ }
242
+ logger.debug(`Resolving ${domains.length} domains in batch...`);
243
+ // Resolve all domains concurrently
244
+ const resolutionPromises = domains.map((domain) => resolveDomain(domain));
245
+ const resolutions = await Promise.all(resolutionPromises);
246
+ // Build domain map and collect errors
247
+ const domainMap = {};
248
+ const errors = [];
249
+ for (const resolution of resolutions) {
250
+ if (resolution.resolved && resolution.address) {
251
+ domainMap[resolution.domain] = resolution.address;
252
+ }
253
+ else if (resolution.error) {
254
+ errors.push(`${resolution.domain}: ${resolution.error}`);
255
+ }
256
+ }
257
+ const successfulResolutions = resolutions.filter((r) => r.resolved).length;
258
+ logger.debug(`Batch resolution complete: ${successfulResolutions}/${domains.length} successful`);
259
+ return {
260
+ success: successfulResolutions > 0,
261
+ resolutions,
262
+ domainMap,
263
+ errors,
264
+ };
265
+ }
266
+ /**
267
+ * Display batch resolution results in a user-friendly format
268
+ *
269
+ * @param {BatchResolutionResult} result - Batch resolution result
270
+ */
271
+ function displayResolutionResults(result) {
272
+ if (result.resolutions.length === 0) {
273
+ console.log(chalk_1.default.yellow('No names to resolve'));
274
+ return;
275
+ }
276
+ // Display successful resolutions
277
+ const successful = result.resolutions.filter((r) => r.resolved);
278
+ if (successful.length > 0) {
279
+ successful.forEach((resolution) => {
280
+ console.log(chalk_1.default.gray(` ${resolution.domain} → ${resolution.address}`));
281
+ });
282
+ }
283
+ // Display failures (but don't make them too prominent)
284
+ const failed = result.resolutions.filter((r) => !r.resolved);
285
+ if (failed.length > 0) {
286
+ failed.forEach((resolution) => {
287
+ console.log(chalk_1.default.yellow(` ✗ ${resolution.domain}: ${resolution.error || 'Could not resolve'}`));
288
+ });
289
+ }
290
+ console.log();
291
+ }