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.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/config.json.example +78 -0
- package/dist/index.js +627 -0
- package/dist/src/ai-orchestrator/index.js +870 -0
- package/dist/src/ai-orchestrator/registry.js +96 -0
- package/dist/src/config.js +352 -0
- package/dist/src/intent-parser/index.js +1342 -0
- package/dist/src/intent-parser/utils.js +108 -0
- package/dist/src/logger.js +72 -0
- package/dist/src/main.js +393 -0
- package/dist/src/types.js +5 -0
- package/dist/src/utilities/address-validator.js +242 -0
- package/dist/src/utilities/domain-resolver.js +291 -0
- package/dist/src/utilities/feed-fetcher.js +387 -0
- package/dist/src/utilities/ff1-device.js +176 -0
- package/dist/src/utilities/functions.js +325 -0
- package/dist/src/utilities/index.js +372 -0
- package/dist/src/utilities/nft-indexer.js +1013 -0
- package/dist/src/utilities/playlist-builder.js +522 -0
- package/dist/src/utilities/playlist-publisher.js +131 -0
- package/dist/src/utilities/playlist-send.js +241 -0
- package/dist/src/utilities/playlist-signer.js +171 -0
- package/dist/src/utilities/playlist-verifier.js +156 -0
- package/dist/src/utils.js +48 -0
- package/docs/CONFIGURATION.md +178 -0
- package/docs/EXAMPLES.md +331 -0
- package/docs/FUNCTION_CALLING.md +92 -0
- package/docs/README.md +267 -0
- package/docs/RELEASING.md +22 -0
- package/package.json +75 -0
|
@@ -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
|
+
}
|