codecrypto-cli 1.0.13 → 1.0.15

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,159 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.generateKeysCommand = void 0;
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const ora_1 = __importDefault(require("ora"));
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
46
+ const ethers_1 = require("ethers");
47
+ exports.generateKeysCommand = new commander_1.Command('generate-keys')
48
+ .description('Generate Foundry keys file with mnemonic and derived accounts')
49
+ .option('-m, --mnemonic <mnemonic>', 'Mnemonic phrase (if not provided, a random one will be generated)')
50
+ .option('-n, --count <number>', 'Number of accounts to generate', '10')
51
+ .option('-o, --output <path>', 'Output file path', '~/.foundry/keys.json')
52
+ .action(async (options) => {
53
+ console.log(chalk_1.default.blue('\nšŸ”‘ CodeCrypto Foundry Keys Generator\n'));
54
+ try {
55
+ // Parse count
56
+ const accountCount = parseInt(options.count, 10);
57
+ if (isNaN(accountCount) || accountCount < 1 || accountCount > 100) {
58
+ console.error(chalk_1.default.red('āŒ Error: Account count must be between 1 and 100'));
59
+ process.exit(1);
60
+ }
61
+ // Generate or use provided mnemonic
62
+ let mnemonic;
63
+ if (options.mnemonic) {
64
+ mnemonic = options.mnemonic.trim();
65
+ // Validate mnemonic
66
+ if (!ethers_1.ethers.Mnemonic.isValidMnemonic(mnemonic)) {
67
+ console.error(chalk_1.default.red('āŒ Error: Invalid mnemonic phrase'));
68
+ process.exit(1);
69
+ }
70
+ }
71
+ else {
72
+ // Generate random mnemonic
73
+ const spinner = (0, ora_1.default)('Generating random mnemonic...').start();
74
+ mnemonic = ethers_1.ethers.Mnemonic.entropyToPhrase(ethers_1.ethers.randomBytes(16));
75
+ spinner.succeed('Random mnemonic generated');
76
+ }
77
+ // Derive accounts
78
+ const accountsSpinner = (0, ora_1.default)(`Deriving ${accountCount} account(s) from mnemonic...`).start();
79
+ const accounts = [];
80
+ try {
81
+ for (let i = 0; i < accountCount; i++) {
82
+ // Use HDNodeWallet.fromPhrase with the full derivation path
83
+ // This creates a wallet directly from mnemonic at the specified path
84
+ const wallet = ethers_1.ethers.HDNodeWallet.fromPhrase(mnemonic, `m/44'/60'/0'/0/${i}`);
85
+ accounts.push({
86
+ address: wallet.address,
87
+ private_key: wallet.privateKey,
88
+ });
89
+ }
90
+ accountsSpinner.succeed(`Generated ${accountCount} account(s)`);
91
+ }
92
+ catch (error) {
93
+ accountsSpinner.fail('Failed to derive accounts');
94
+ console.error(chalk_1.default.red(`āŒ Error: ${error.message}`));
95
+ process.exit(1);
96
+ }
97
+ // Prepare output data
98
+ const keysData = {
99
+ mnemonic,
100
+ accounts,
101
+ };
102
+ // Resolve output path
103
+ let outputPath = options.output;
104
+ if (outputPath.startsWith('~')) {
105
+ outputPath = path.join(os.homedir(), outputPath.slice(1));
106
+ }
107
+ outputPath = path.resolve(outputPath);
108
+ // Create directory if it doesn't exist
109
+ const outputDir = path.dirname(outputPath);
110
+ if (!fs.existsSync(outputDir)) {
111
+ const dirSpinner = (0, ora_1.default)(`Creating directory: ${outputDir}`).start();
112
+ try {
113
+ fs.mkdirSync(outputDir, { recursive: true });
114
+ dirSpinner.succeed(`Directory created: ${outputDir}`);
115
+ }
116
+ catch (error) {
117
+ dirSpinner.fail('Failed to create directory');
118
+ console.error(chalk_1.default.red(`āŒ Error: ${error.message}`));
119
+ process.exit(1);
120
+ }
121
+ }
122
+ // Write file
123
+ const writeSpinner = (0, ora_1.default)(`Writing keys to ${outputPath}...`).start();
124
+ try {
125
+ fs.writeFileSync(outputPath, JSON.stringify(keysData, null, 2), 'utf-8');
126
+ writeSpinner.succeed(`Keys saved to ${outputPath}`);
127
+ }
128
+ catch (error) {
129
+ writeSpinner.fail('Failed to write file');
130
+ console.error(chalk_1.default.red(`āŒ Error: ${error.message}`));
131
+ process.exit(1);
132
+ }
133
+ // Display summary
134
+ console.log(chalk_1.default.green('\nāœ… Keys generated successfully!\n'));
135
+ console.log(chalk_1.default.gray('Summary:'));
136
+ console.log(chalk_1.default.white(` Mnemonic: ${chalk_1.default.cyan(mnemonic)}`));
137
+ console.log(chalk_1.default.white(` Accounts: ${chalk_1.default.cyan(accountCount)}`));
138
+ console.log(chalk_1.default.white(` Output: ${chalk_1.default.cyan(outputPath)}\n`));
139
+ // Display first few accounts
140
+ console.log(chalk_1.default.gray('First 3 accounts:'));
141
+ accounts.slice(0, 3).forEach((account, index) => {
142
+ console.log(chalk_1.default.white(` ${index + 1}. ${chalk_1.default.cyan(account.address)}`));
143
+ console.log(chalk_1.default.gray(` Private Key: ${chalk_1.default.dim(account.private_key)}`));
144
+ });
145
+ if (accountCount > 3) {
146
+ console.log(chalk_1.default.gray(` ... and ${accountCount - 3} more account(s)\n`));
147
+ }
148
+ else {
149
+ console.log('');
150
+ }
151
+ console.log(chalk_1.default.yellow('āš ļø Warning: Keep your mnemonic and private keys secure!'));
152
+ console.log(chalk_1.default.yellow(' Never share them or commit them to version control.\n'));
153
+ }
154
+ catch (error) {
155
+ console.error(chalk_1.default.red(`\nāŒ Error: ${error.message}`));
156
+ process.exit(1);
157
+ }
158
+ });
159
+ //# sourceMappingURL=generate-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-keys.js","sourceRoot":"","sources":["../../src/commands/generate-keys.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ const deploy_1 = require("./commands/deploy");
42
42
  const deploy_sc_1 = require("./commands/deploy-sc");
43
43
  const auth_1 = require("./commands/auth");
44
44
  const doctor_1 = require("./commands/doctor");
45
+ const generate_keys_1 = require("./commands/generate-keys");
45
46
  // Cargar variables de entorno desde ~/.codecrypto/.env si existe
46
47
  function loadEnvFile() {
47
48
  const homeDir = os.homedir();
@@ -81,5 +82,6 @@ program.addCommand(deploy_1.deployCommand);
81
82
  program.addCommand(deploy_sc_1.deployScCommand);
82
83
  program.addCommand(auth_1.authCommand);
83
84
  program.addCommand(doctor_1.doctorCommand);
85
+ program.addCommand(generate_keys_1.generateKeysCommand);
84
86
  program.parse(process.argv);
85
87
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,yCAAoC;AACpC,uCAAyB;AACzB,2CAA6B;AAC7B,uCAAyB;AACzB,8CAAkD;AAClD,oDAAuD;AACvD,0CAA8C;AAC9C,8CAAkD;AAElD,iEAAiE;AACjE,SAAS,WAAW;IAClB,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC7B,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAErD,sCAAsC;IACtC,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAExC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,sCAAsC;YACtC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAElD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,UAAU,KAAK,CAAC,CAAC;gBAAE,SAAS;YAEhC,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAEvD,qGAAqG;YACrG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IACD,6FAA6F;AAC/F,CAAC;AAED,yCAAyC;AACzC,WAAW,EAAE,CAAC;AAEd,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,oCAAoC,CAAC;KACjD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,qBAAqB;AACrB,OAAO,CAAC,UAAU,CAAC,sBAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,2BAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,kBAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,sBAAa,CAAC,CAAC;AAElC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,yCAAoC;AACpC,uCAAyB;AACzB,2CAA6B;AAC7B,uCAAyB;AACzB,8CAAkD;AAClD,oDAAuD;AACvD,0CAA8C;AAC9C,8CAAkD;AAClD,4DAA+D;AAE/D,iEAAiE;AACjE,SAAS,WAAW;IAClB,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC7B,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAErD,sCAAsC;IACtC,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAExC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,sCAAsC;YACtC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAElD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,UAAU,KAAK,CAAC,CAAC;gBAAE,SAAS;YAEhC,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAEvD,qGAAqG;YACrG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IACD,6FAA6F;AAC/F,CAAC;AAED,yCAAyC;AACzC,WAAW,EAAE,CAAC;AAEd,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,oCAAoC,CAAC;KACjD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,qBAAqB;AACrB,OAAO,CAAC,UAAU,CAAC,sBAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,2BAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,kBAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,sBAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,mCAAmB,CAAC,CAAC;AAExC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecrypto-cli",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "CLI tool for CodeCrypto operations",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -22,6 +22,7 @@
22
22
  "dependencies": {
23
23
  "chalk": "^4.1.2",
24
24
  "commander": "^12.0.0",
25
+ "ethers": "^6.9.0",
25
26
  "inquirer": "^8.2.5",
26
27
  "jose": "^6.1.3",
27
28
  "ora": "^5.4.1"
@@ -29,8 +30,8 @@
29
30
  "devDependencies": {
30
31
  "@types/inquirer": "^9.0.7",
31
32
  "@types/node": "^20.10.0",
32
- "tsx": "^4.7.0",
33
- "typescript": "^5.3.0"
33
+ "tsx": "^4.21.0",
34
+ "typescript": "^5.9.3"
34
35
  },
35
36
  "engines": {
36
37
  "node": ">=16.0.0"
@@ -25,7 +25,7 @@ interface TokenVerificationResponse {
25
25
  error?: string;
26
26
  }
27
27
 
28
- const API_BASE_URL = process.env.CODECRYPTO_API_URL || process.env.API_URL || 'http://localhost:3000';
28
+ const API_BASE_URL = process.env.CODECRYPTO_API_URL || process.env.API_URL || 'https://proyectos.codecrypto.academy';
29
29
  const POLL_INTERVAL = 3000; // 3 segundos
30
30
  const MAX_POLL_ATTEMPTS = 100; // MĆ”ximo 5 minutos (100 * 3 segundos)
31
31
 
@@ -5,11 +5,12 @@ import { execSync } from 'child_process';
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import * as os from 'os';
8
+ import { ethers } from 'ethers';
8
9
 
9
10
  export const deployScCommand = new Command('deploy-sc')
10
11
  .description('Deploy smart contract using Foundry')
11
12
  .argument('<script-path>', 'Path to the Foundry deployment script')
12
- .option('-u, --url <rpc-url>', 'RPC URL of the blockchain', 'https://besu1.codecrypto.academy')
13
+ .option('-u, --url <rpc-url>', 'RPC URL of the blockchain', 'https://besu1.proyectos.codecrypto.academy')
13
14
  .option('-k, --private-key <key>', 'Private key for deployment (required unless using --anvil or --account)')
14
15
  .option('--account <index>', 'Account index to use (for Anvil, use 0 for the default unlocked account)')
15
16
  .option('--anvil', 'Force use of Anvil account 0 (ignores private-key)')
@@ -156,6 +157,44 @@ export const deployScCommand = new Command('deploy-sc')
156
157
  forgeArgs.push('--slow');
157
158
  }
158
159
 
160
+ // Validar saldo antes de desplegar (solo si se usa private-key)
161
+ if (options.privateKey && !options.anvil && options.account === undefined) {
162
+ const balanceSpinner = ora('Verificando saldo de la cuenta...').start();
163
+ try {
164
+ const provider = new ethers.JsonRpcProvider(options.url);
165
+ const wallet = new ethers.Wallet(options.privateKey, provider);
166
+ const address = wallet.address;
167
+ const balance = await provider.getBalance(address);
168
+
169
+ // Convertir a ETH (mĆ­nimo 1 ETH recomendado)
170
+ const balanceInEth = ethers.formatEther(balance);
171
+ const minBalance = ethers.parseEther('1.0');
172
+
173
+ balanceSpinner.succeed(`Saldo verificado: ${balanceInEth} ETH`);
174
+
175
+ if (balance < minBalance) {
176
+ console.error(chalk.red('\nāŒ Error: Saldo insuficiente en la cuenta'));
177
+ console.error(chalk.red(` Dirección: ${address}`));
178
+ console.error(chalk.red(` Saldo actual: ${balanceInEth} ETH`));
179
+ console.error(chalk.red(` Saldo mĆ­nimo recomendado: 1.0 ETH`));
180
+ console.error(chalk.yellow('\nšŸ’” AsegĆŗrate de tener suficiente saldo para pagar las fees de gas'));
181
+ console.log(chalk.cyan('\n🚰 Obtén fondos del faucet:'));
182
+ console.log(chalk.cyan(` ${chalk.underline('https://faucet.codecrypto.academy')}`));
183
+ console.log(chalk.gray(` Visita el faucet y solicita fondos para tu dirección: ${address}\n`));
184
+ process.exit(1);
185
+ }
186
+
187
+ console.log(chalk.green(` Dirección: ${chalk.cyan(address)}`));
188
+ console.log(chalk.green(` Saldo: ${chalk.cyan(balanceInEth)} ETH\n`));
189
+ } catch (error: any) {
190
+ balanceSpinner.warn(`No se pudo verificar el saldo: ${error.message}`);
191
+ console.log(chalk.yellow(' Continuando con el despliegue...'));
192
+ console.log(chalk.yellow(' AsegĆŗrate de tener suficiente saldo para las fees de gas'));
193
+ console.log(chalk.cyan('\n🚰 Si necesitas fondos, usa el faucet:'));
194
+ console.log(chalk.cyan(` ${chalk.underline('https://faucet.codecrypto.academy')}\n`));
195
+ }
196
+ }
197
+
159
198
  // Ejecutar el script
160
199
  const deploySpinner = ora('Desplegando smart contract...').start();
161
200
 
@@ -258,7 +297,8 @@ async function uploadDeploymentResults(config: {
258
297
  }
259
298
 
260
299
  const email = tokenData.email;
261
- const serverUrl = tokenData.serverUrl || process.env.CODECRYPTO_API_URL || process.env.API_URL || 'http://localhost:3000';
300
+ // URL especĆ­fica para subir smart contracts
301
+ const uploadServerUrl = process.env.CODECRYPTO_UPLOAD_URL || process.env.UPLOAD_SMART_CONTRACT_URL || 'https://proyectos.proyectos.codecrypto.academy';
262
302
  const token = tokenData.token;
263
303
 
264
304
  if (!email) {
@@ -323,7 +363,7 @@ async function uploadDeploymentResults(config: {
323
363
  }
324
364
 
325
365
  // 6. Hacer la llamada POST al endpoint
326
- const apiUrl = `${serverUrl}/api/uploadSmartContract`;
366
+ const apiUrl = `${uploadServerUrl}/api/uploadSmartContract`;
327
367
 
328
368
  // Log de la petición (solo si debug estĆ” activado)
329
369
  if (config.debug) {
@@ -154,12 +154,79 @@ export const doctorCommand = new Command('doctor')
154
154
  try {
155
155
  const tokenData = JSON.parse(fs.readFileSync(tokenFilePath, 'utf-8'));
156
156
  if (tokenData.token && tokenData.email) {
157
- tokenCheck.succeed(`Token found for: ${tokenData.email}`);
158
- checks.push({
159
- name: 'CodeCrypto Token',
160
- status: 'success',
161
- message: `Token found for: ${tokenData.email}`
162
- });
157
+ // Validar el token con el servidor
158
+ const serverUrl = tokenData.serverUrl || process.env.CODECRYPTO_API_URL || process.env.API_URL || 'https://proyectos.codecrypto.academy';
159
+ const token = String(tokenData.token).trim();
160
+
161
+ try {
162
+ const verifyUrl = `${serverUrl}/api/tokens/verify`;
163
+ const controller = new AbortController();
164
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 seconds timeout
165
+
166
+ const response = await fetch(verifyUrl, {
167
+ method: 'GET',
168
+ headers: {
169
+ 'Authorization': `Bearer ${token}`,
170
+ },
171
+ signal: controller.signal,
172
+ });
173
+
174
+ clearTimeout(timeoutId);
175
+
176
+ if (response.ok) {
177
+ const responseData = await response.json();
178
+ tokenCheck.succeed(`Token valid for: ${tokenData.email}`);
179
+ checks.push({
180
+ name: 'CodeCrypto Token',
181
+ status: 'success',
182
+ message: `Token valid for: ${tokenData.email}`
183
+ });
184
+ } else {
185
+ const errorText = await response.text();
186
+ let errorMessage = 'Token validation failed';
187
+ try {
188
+ const errorJson = JSON.parse(errorText);
189
+ errorMessage = errorJson.error || errorJson.message || errorMessage;
190
+ } catch {
191
+ errorMessage = errorText || errorMessage;
192
+ }
193
+
194
+ tokenCheck.fail(`Token validation failed: ${errorMessage}`);
195
+ checks.push({
196
+ name: 'CodeCrypto Token',
197
+ status: 'error',
198
+ message: `Token validation failed: ${errorMessage}`,
199
+ details: 'Run "codecrypto auth --force" to refresh your token'
200
+ });
201
+ }
202
+ } catch (fetchError: any) {
203
+ const errorMsg = fetchError.message || String(fetchError);
204
+ if (fetchError.name === 'AbortError' || errorMsg.includes('timeout')) {
205
+ tokenCheck.fail('Token validation timeout');
206
+ checks.push({
207
+ name: 'CodeCrypto Token',
208
+ status: 'error',
209
+ message: 'Token validation timeout',
210
+ details: 'Cannot reach server to validate token. Check your internet connection.'
211
+ });
212
+ } else if (errorMsg.includes('ECONNREFUSED') || errorMsg.includes('ENOTFOUND')) {
213
+ tokenCheck.fail('Cannot reach server to validate token');
214
+ checks.push({
215
+ name: 'CodeCrypto Token',
216
+ status: 'error',
217
+ message: 'Cannot reach server to validate token',
218
+ details: `Cannot reach ${serverUrl}. Check your network connection.`
219
+ });
220
+ } else {
221
+ tokenCheck.fail(`Token validation error: ${errorMsg}`);
222
+ checks.push({
223
+ name: 'CodeCrypto Token',
224
+ status: 'error',
225
+ message: `Token validation error: ${errorMsg}`,
226
+ details: 'Run "codecrypto auth --force" to refresh your token'
227
+ });
228
+ }
229
+ }
163
230
  } else {
164
231
  tokenCheck.fail('Token file exists but is invalid');
165
232
  checks.push({
@@ -188,7 +255,69 @@ export const doctorCommand = new Command('doctor')
188
255
  });
189
256
  }
190
257
 
191
- // 8. Check Docker certificates in token.json
258
+ // 8. Check server access (proyectos.codecrypto.academy)
259
+ const serverCheck = ora('Checking access to proyectos.codecrypto.academy...').start();
260
+ try {
261
+ const serverUrl = 'https://proyectos.codecrypto.academy';
262
+ const controller = new AbortController();
263
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 seconds timeout
264
+
265
+ const response = await fetch(serverUrl, {
266
+ method: 'GET',
267
+ signal: controller.signal,
268
+ headers: {
269
+ 'User-Agent': 'CodeCrypto-CLI/1.0'
270
+ }
271
+ });
272
+
273
+ clearTimeout(timeoutId);
274
+
275
+ if (response.ok || response.status < 500) {
276
+ serverCheck.succeed(`Server accessible: ${serverUrl}`);
277
+ checks.push({
278
+ name: 'Server Access',
279
+ status: 'success',
280
+ message: `Server accessible: ${serverUrl} (status: ${response.status})`
281
+ });
282
+ } else {
283
+ serverCheck.fail(`Server returned error: ${response.status}`);
284
+ checks.push({
285
+ name: 'Server Access',
286
+ status: 'error',
287
+ message: `Server returned error: ${response.status}`,
288
+ details: `Check if ${serverUrl} is accessible from your network`
289
+ });
290
+ }
291
+ } catch (error: any) {
292
+ const errorMsg = error.message || String(error);
293
+ if (error.name === 'AbortError' || errorMsg.includes('timeout')) {
294
+ serverCheck.fail('Server connection timeout');
295
+ checks.push({
296
+ name: 'Server Access',
297
+ status: 'error',
298
+ message: 'Server connection timeout',
299
+ details: `Cannot reach https://proyectos.codecrypto.academy. Check your internet connection.`
300
+ });
301
+ } else if (errorMsg.includes('ECONNREFUSED') || errorMsg.includes('ENOTFOUND') || errorMsg.includes('getaddrinfo')) {
302
+ serverCheck.fail('Cannot resolve server address');
303
+ checks.push({
304
+ name: 'Server Access',
305
+ status: 'error',
306
+ message: 'Cannot resolve server address',
307
+ details: `Cannot reach https://proyectos.codecrypto.academy. Check DNS resolution and network connectivity.`
308
+ });
309
+ } else {
310
+ serverCheck.fail('Cannot access server');
311
+ checks.push({
312
+ name: 'Server Access',
313
+ status: 'error',
314
+ message: 'Cannot access server',
315
+ details: `Error: ${errorMsg}. Check if https://proyectos.codecrypto.academy is accessible.`
316
+ });
317
+ }
318
+ }
319
+
320
+ // 9. Check Docker certificates in token.json
192
321
  const certCheck = ora('Checking Docker certificates in token.json...').start();
193
322
  try {
194
323
  if (fs.existsSync(tokenFilePath)) {
@@ -254,7 +383,7 @@ export const doctorCommand = new Command('doctor')
254
383
  });
255
384
  }
256
385
 
257
- // 9. Check Git (optional but recommended)
386
+ // 10. Check Git (optional but recommended)
258
387
  const gitCheck = ora('Checking Git...').start();
259
388
  try {
260
389
  const gitVersion = execSync('git --version', { encoding: 'utf-8', stdio: 'pipe' }).trim();
@@ -279,7 +408,7 @@ export const doctorCommand = new Command('doctor')
279
408
  // ============================================
280
409
  console.log(chalk.cyan('\nšŸ”· Foundry / Smart Contracts Deployment Checks\n'));
281
410
 
282
- // 10. Check Foundry (forge)
411
+ // 11. Check Foundry (forge)
283
412
  const forgeCheck = ora('Checking Foundry (forge)...').start();
284
413
  try {
285
414
  const forgeVersion = execSync('forge --version', { encoding: 'utf-8', stdio: 'pipe' }).trim();
@@ -299,7 +428,7 @@ export const doctorCommand = new Command('doctor')
299
428
  });
300
429
  }
301
430
 
302
- // 11. Check cast
431
+ // 12. Check cast
303
432
  const castCheck = ora('Checking cast...').start();
304
433
  try {
305
434
  const castVersion = execSync('cast --version', { encoding: 'utf-8', stdio: 'pipe' }).trim();
@@ -319,7 +448,7 @@ export const doctorCommand = new Command('doctor')
319
448
  });
320
449
  }
321
450
 
322
- // 12. Check anvil
451
+ // 13. Check anvil
323
452
  const anvilCheck = ora('Checking anvil...').start();
324
453
  try {
325
454
  const anvilVersion = execSync('anvil --version', { encoding: 'utf-8', stdio: 'pipe' }).trim();
@@ -339,7 +468,7 @@ export const doctorCommand = new Command('doctor')
339
468
  });
340
469
  }
341
470
 
342
- // 13. Check if Anvil is running on correct port and chain-id
471
+ // 14. Check if Anvil is running on correct port and chain-id
343
472
  const anvilRunningCheck = ora('Checking if Anvil is running (port 8545, chain-id 31337)...').start();
344
473
  try {
345
474
  // Try to get chain id from the RPC
@@ -402,7 +531,7 @@ export const doctorCommand = new Command('doctor')
402
531
  }
403
532
  }
404
533
 
405
- // 14. Check Anvil account 0
534
+ // 15. Check Anvil account 0
406
535
  const anvilAccountCheck = ora('Checking Anvil account 0...').start();
407
536
  try {
408
537
  const rpcUrl = 'http://localhost:8545';
@@ -427,6 +556,108 @@ export const doctorCommand = new Command('doctor')
427
556
  });
428
557
  }
429
558
 
559
+ // 16. Check Besu1 network access
560
+ const besu1Check = ora('Checking Besu1 network (https://besu1.proyectos.codecrypto.academy)...').start();
561
+ try {
562
+ const rpcUrl = 'https://besu1.proyectos.codecrypto.academy';
563
+ const chainIdResponse = execSync(
564
+ `cast chain-id --rpc-url ${rpcUrl}`,
565
+ { encoding: 'utf-8', stdio: 'pipe', timeout: 10000 }
566
+ ).trim();
567
+
568
+ let chainId: number;
569
+ if (chainIdResponse.startsWith('0x') || chainIdResponse.startsWith('0X')) {
570
+ chainId = parseInt(chainIdResponse, 16);
571
+ } else {
572
+ chainId = parseInt(chainIdResponse, 10);
573
+ }
574
+
575
+ besu1Check.succeed(`Besu1 network accessible (chain-id: ${chainId})`);
576
+ checks.push({
577
+ name: 'Besu1 Network',
578
+ status: 'success',
579
+ message: `Besu1 network accessible: ${rpcUrl} (chain-id: ${chainId})`
580
+ });
581
+ } catch (error: any) {
582
+ const errorMsg = error.message || String(error);
583
+ if (errorMsg.includes('ECONNREFUSED') || errorMsg.includes('Connection refused')) {
584
+ besu1Check.fail('Cannot connect to Besu1 network');
585
+ checks.push({
586
+ name: 'Besu1 Network',
587
+ status: 'error',
588
+ message: 'Cannot connect to Besu1 network',
589
+ details: 'Check if https://besu1.proyectos.codecrypto.academy is accessible from your network'
590
+ });
591
+ } else if (errorMsg.includes('timeout') || errorMsg.includes('ETIMEDOUT')) {
592
+ besu1Check.fail('Besu1 network connection timeout');
593
+ checks.push({
594
+ name: 'Besu1 Network',
595
+ status: 'error',
596
+ message: 'Besu1 network connection timeout',
597
+ details: 'Check your internet connection and network access to https://besu1.proyectos.codecrypto.academy'
598
+ });
599
+ } else {
600
+ besu1Check.fail('Cannot access Besu1 network');
601
+ checks.push({
602
+ name: 'Besu1 Network',
603
+ status: 'error',
604
+ message: 'Cannot access Besu1 network',
605
+ details: `Error: ${errorMsg}. Check if https://besu1.proyectos.codecrypto.academy is accessible.`
606
+ });
607
+ }
608
+ }
609
+
610
+ // 17. Check Besu2 network access
611
+ const besu2Check = ora('Checking Besu2 network (https://besu2.proyectos.codecrypto.academy)...').start();
612
+ try {
613
+ const rpcUrl = 'https://besu2.proyectos.codecrypto.academy';
614
+ const chainIdResponse = execSync(
615
+ `cast chain-id --rpc-url ${rpcUrl}`,
616
+ { encoding: 'utf-8', stdio: 'pipe', timeout: 10000 }
617
+ ).trim();
618
+
619
+ let chainId: number;
620
+ if (chainIdResponse.startsWith('0x') || chainIdResponse.startsWith('0X')) {
621
+ chainId = parseInt(chainIdResponse, 16);
622
+ } else {
623
+ chainId = parseInt(chainIdResponse, 10);
624
+ }
625
+
626
+ besu2Check.succeed(`Besu2 network accessible (chain-id: ${chainId})`);
627
+ checks.push({
628
+ name: 'Besu2 Network',
629
+ status: 'success',
630
+ message: `Besu2 network accessible: ${rpcUrl} (chain-id: ${chainId})`
631
+ });
632
+ } catch (error: any) {
633
+ const errorMsg = error.message || String(error);
634
+ if (errorMsg.includes('ECONNREFUSED') || errorMsg.includes('Connection refused')) {
635
+ besu2Check.fail('Cannot connect to Besu2 network');
636
+ checks.push({
637
+ name: 'Besu2 Network',
638
+ status: 'error',
639
+ message: 'Cannot connect to Besu2 network',
640
+ details: 'Check if https://besu2.proyectos.codecrypto.academy is accessible from your network'
641
+ });
642
+ } else if (errorMsg.includes('timeout') || errorMsg.includes('ETIMEDOUT')) {
643
+ besu2Check.fail('Besu2 network connection timeout');
644
+ checks.push({
645
+ name: 'Besu2 Network',
646
+ status: 'error',
647
+ message: 'Besu2 network connection timeout',
648
+ details: 'Check your internet connection and network access to https://besu2.proyectos.codecrypto.academy'
649
+ });
650
+ } else {
651
+ besu2Check.fail('Cannot access Besu2 network');
652
+ checks.push({
653
+ name: 'Besu2 Network',
654
+ status: 'error',
655
+ message: 'Cannot access Besu2 network',
656
+ details: `Error: ${errorMsg}. Check if https://besu2.proyectos.codecrypto.academy is accessible.`
657
+ });
658
+ }
659
+ }
660
+
430
661
  // ============================================
431
662
  // SUMMARY
432
663
  // ============================================