aether-hub 1.1.2 → 1.1.3

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/commands/init.js CHANGED
@@ -501,7 +501,7 @@ async function connectTestnet(rl, tier = 'full') {
501
501
  rl.close();
502
502
 
503
503
  const validatorStart = require('./validator-start');
504
- validatorStart.validatorStart(tier);
504
+ validatorStart.validatorStart({ testnet: true, tier });
505
505
  return true;
506
506
  }
507
507
  } catch (err) {
@@ -64,8 +64,9 @@ function findValidatorBinary() {
64
64
 
65
65
  /**
66
66
  * Parse command line args for validator-start
67
+ * @param {Object} overrideOptions - Options passed directly (e.g. from init.js)
67
68
  */
68
- function parseArgs() {
69
+ function parseArgs(overrideOptions = {}) {
69
70
  const args = process.argv.slice(3); // Skip 'aether-cli validator start'
70
71
 
71
72
  const options = {
@@ -75,6 +76,7 @@ function parseArgs() {
75
76
  identity: null,
76
77
  verbose: false,
77
78
  tier: 'full',
79
+ ...overrideOptions, // Allow init.js to pass testnet/tier directly
78
80
  };
79
81
 
80
82
  for (let i = 0; i < args.length; i++) {
@@ -121,7 +123,10 @@ ${colors.cyan}╚═════════════════════
121
123
  `);
122
124
 
123
125
  console.log(` ${colors.bright}Network:${colors.reset}`);
124
- console.log(` Mode: ${options.testnet ? colors.yellow + 'TESTNET' : colors.green + 'MAINNET (not implemented)'}`);
126
+ const modeStr = options.testnet
127
+ ? colors.yellow + 'TESTNET'
128
+ : colors.red + 'MAINNET';
129
+ console.log(` Mode: ${modeStr}`);
125
130
  console.log(` Tier: ${tierLabel}`);
126
131
  console.log(` RPC: http://${options.rpcAddr}`);
127
132
  console.log(` P2P: ${options.p2pAddr}`);
@@ -136,16 +141,38 @@ ${colors.cyan}╚═════════════════════
136
141
  */
137
142
  function buildValidator() {
138
143
  const { execSync } = require('child_process');
144
+ const platform = os.platform();
145
+ const isWindows = platform === 'win32';
139
146
  const workspaceRoot = path.join(__dirname, '..', '..');
140
147
  const repoPath = path.join(workspaceRoot, 'Jelly-legs-unsteady-workshop');
141
148
 
142
149
  console.log(` ${colors.cyan}Building aether-validator...${colors.reset}`);
143
150
 
144
151
  try {
145
- execSync('cargo build --bin aether-validator', {
152
+ // Use full cargo path on Windows to avoid spawnSync ENOENT
153
+ const cargoPaths = isWindows
154
+ ? [
155
+ path.join(process.env.USERPROFILE || '', '.cargo', 'bin', 'cargo.exe'),
156
+ path.join(process.env.LOCALAPPDATA || '', 'Rust', 'bin', 'cargo.exe'),
157
+ 'C:\\Users\\RM_Ga\\.cargo\\bin\\cargo.exe',
158
+ 'cargo',
159
+ ]
160
+ : ['cargo'];
161
+
162
+ let cargoCmd = 'cargo';
163
+ for (const cp of cargoPaths) {
164
+ if (cp === 'cargo' || fs.existsSync(cp)) {
165
+ cargoCmd = cp;
166
+ break;
167
+ }
168
+ }
169
+
170
+ console.log(` ${colors.cyan}Running: ${cargoCmd} build --release --package aether-validator${colors.reset}`);
171
+
172
+ // Use execSync WITHOUT shell:true — avoids Windows spawnSync cmd.exe ENOENT
173
+ execSync(`${cargoCmd} build --release --package aether-validator`, {
146
174
  cwd: repoPath,
147
175
  stdio: 'inherit',
148
- shell: true,
149
176
  });
150
177
 
151
178
  // Re-check for binary
@@ -165,18 +192,24 @@ function buildValidator() {
165
192
 
166
193
  /**
167
194
  * Main validator start command
195
+ * @param {Object|null} options - { testnet?: boolean, tier?: string }
168
196
  */
169
- function validatorStart(overrideTier = null) {
170
- const options = parseArgs();
197
+ function validatorStart(options = {}) {
198
+ // Support both old string-style (tier only) and new object-style { testnet, tier }
199
+ const parsedArgs = parseArgs(typeof options === 'object' ? options : { tier: options });
200
+ const optionsObj = typeof options === 'object' ? options : {};
171
201
 
172
- // Allow tier override from init.js
173
- if (overrideTier) {
174
- options.tier = overrideTier;
175
- }
202
+ // Merge: explicit options override parseArgs defaults
203
+ const finalOptions = {
204
+ ...parsedArgs,
205
+ ...optionsObj,
206
+ tier: optionsObj.tier || parsedArgs.tier,
207
+ testnet: optionsObj.testnet !== undefined ? optionsObj.testnet : parsedArgs.testnet,
208
+ };
176
209
 
177
210
  let result = findValidatorBinary();
178
211
 
179
- printBanner(options);
212
+ printBanner(finalOptions);
180
213
 
181
214
  // Handle missing binary
182
215
  if (result.type === 'missing') {
@@ -203,12 +236,12 @@ function validatorStart(overrideTier = null) {
203
236
  process.exit(1);
204
237
  }
205
238
  result = built;
206
- startValidatorProcess(result, options);
239
+ startValidatorProcess(result, finalOptions);
207
240
  });
208
241
  return;
209
242
  }
210
243
 
211
- startValidatorProcess(result, options);
244
+ startValidatorProcess(result, finalOptions);
212
245
  }
213
246
 
214
247
  /**
@@ -1106,6 +1106,143 @@ async function txHistory(rl) {
1106
1106
  }
1107
1107
  }
1108
1108
 
1109
+ // ---------------------------------------------------------------------------
1110
+ // STAKE POSITIONS
1111
+ // Query and display current stake positions/delegations for a wallet
1112
+ // ---------------------------------------------------------------------------
1113
+
1114
+ async function stakePositions(rl) {
1115
+ console.log(`\n${C.bright}${C.cyan}── Stake Positions ──────────────────────────────────────${C.reset}\n`);
1116
+
1117
+ const args = process.argv.slice(4);
1118
+ let address = null;
1119
+ let asJson = false;
1120
+
1121
+ const addrIdx = args.findIndex((a) => a === '--address' || a === '-a');
1122
+ if (addrIdx !== -1 && args[addrIdx + 1]) {
1123
+ address = args[addrIdx + 1];
1124
+ }
1125
+
1126
+ asJson = args.includes('--json') || args.includes('-j');
1127
+
1128
+ if (!address) {
1129
+ const cfg = loadConfig();
1130
+ address = cfg.defaultWallet;
1131
+ }
1132
+
1133
+ if (!address) {
1134
+ console.log(` ${C.red}✗ No wallet address specified and no default wallet set.${C.reset}`);
1135
+ console.log(` ${C.dim}Usage: aether wallet stake-positions --address <addr> [--json]${C.reset}\n`);
1136
+ return;
1137
+ }
1138
+
1139
+ const rpcUrl = getDefaultRpc();
1140
+ const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
1141
+
1142
+ if (!asJson) {
1143
+ console.log(` ${C.green}★${C.reset} Address: ${C.bright}${address}${C.reset}`);
1144
+ console.log(` ${C.dim} RPC: ${rpcUrl}${C.reset}`);
1145
+ console.log();
1146
+ }
1147
+
1148
+ try {
1149
+ // Fetch stake accounts for this address
1150
+ const res = await httpRequest(rpcUrl, `/v1/stake?address=${encodeURIComponent(rawAddr)}`);
1151
+
1152
+ let stakeAccounts = [];
1153
+ if (res && !res.error) {
1154
+ stakeAccounts = Array.isArray(res) ? res : (res.accounts || res.stake_accounts || []);
1155
+ }
1156
+
1157
+ if (asJson) {
1158
+ const out = {
1159
+ address,
1160
+ rpc: rpcUrl,
1161
+ stake_accounts: stakeAccounts.map(acc => ({
1162
+ stake_account: acc.pubkey || acc.publicKey || acc.account,
1163
+ validator: acc.validator || acc.voter || acc.vote_account,
1164
+ stake_lamports: acc.stake_lamports || acc.lamports || 0,
1165
+ stake_aeth: (acc.stake_lamports || acc.lamports || 0) / 1e9,
1166
+ status: acc.status || acc.state || 'unknown',
1167
+ activation_epoch: acc.activation_epoch,
1168
+ deactivation_epoch: acc.deactivation_epoch,
1169
+ rewards_earned: acc.rewards_earned || 0,
1170
+ })),
1171
+ total_staked_lamports: stakeAccounts.reduce((sum, acc) => sum + (acc.stake_lamports || acc.lamports || 0), 0),
1172
+ fetched_at: new Date().toISOString(),
1173
+ };
1174
+ console.log(JSON.stringify(out, null, 2));
1175
+ return;
1176
+ }
1177
+
1178
+ if (!stakeAccounts || stakeAccounts.length === 0) {
1179
+ console.log(` ${C.yellow}⚠ No active stake positions found.${C.reset}`);
1180
+ console.log(` ${C.dim} This wallet has not delegated to any validators.${C.reset}`);
1181
+ console.log(` ${C.dim} Stake AETH with: ${C.cyan}aether stake --validator <addr> --amount <aeth>${C.reset}\n`);
1182
+ return;
1183
+ }
1184
+
1185
+ let totalStaked = 0;
1186
+ let activeCount = 0;
1187
+ let deactivatingCount = 0;
1188
+ let inactiveCount = 0;
1189
+
1190
+ console.log(` ${C.bright}Stake Positions (${stakeAccounts.length})${C.reset}\n`);
1191
+
1192
+ const statusColors = {
1193
+ active: C.green,
1194
+ activating: C.cyan,
1195
+ deactivating: C.yellow,
1196
+ inactive: C.dim,
1197
+ };
1198
+
1199
+ for (const acc of stakeAccounts) {
1200
+ const stakeAcct = acc.pubkey || acc.publicKey || acc.account || 'unknown';
1201
+ const validator = acc.validator || acc.voter || acc.vote_account || 'unknown';
1202
+ const lamports = acc.stake_lamports || acc.lamports || 0;
1203
+ const aeth = lamports / 1e9;
1204
+ const status = (acc.status || acc.state || 'unknown').toLowerCase();
1205
+ const rewards = acc.rewards_earned || 0;
1206
+
1207
+ totalStaked += lamports;
1208
+
1209
+ if (status === 'active') activeCount++;
1210
+ else if (status === 'deactivating' || status === 'deactivated') deactivatingCount++;
1211
+ else inactiveCount++;
1212
+
1213
+ const statusColor = statusColors[status] || C.reset;
1214
+ const shortAcct = stakeAcct.length > 20 ? stakeAcct.slice(0, 8) + '…' + stakeAcct.slice(-8) : stakeAcct;
1215
+ const shortVal = validator.length > 20 ? validator.slice(0, 8) + '…' + validator.slice(-8) : validator;
1216
+
1217
+ console.log(` ${C.dim}┌─ ${C.bright}${statusColor}${status.toUpperCase()}${C.reset}`);
1218
+ console.log(` │ ${C.dim}Stake acct:${C.reset} ${shortAcct}`);
1219
+ console.log(` │ ${C.dim}Validator:${C.reset} ${shortVal}`);
1220
+ console.log(` │ ${C.dim}Staked:${C.reset} ${C.bright}${aeth.toFixed(4)} AETH${C.reset} (${lamports.toLocaleString()} lamports)`);
1221
+ if (rewards > 0) {
1222
+ console.log(` │ ${C.dim}Rewards:${C.reset} ${C.green}+${(rewards / 1e9).toFixed(4)} AETH${C.reset}`);
1223
+ }
1224
+ if (acc.activation_epoch !== undefined) {
1225
+ console.log(` │ ${C.dim}Activated:${C.reset} epoch ${acc.activation_epoch}`);
1226
+ }
1227
+ if (acc.deactivation_epoch !== undefined) {
1228
+ console.log(` │ ${C.dim}Deactivates:${C.reset} epoch ${acc.deactivation_epoch}`);
1229
+ }
1230
+ console.log(` ${C.dim}└${C.reset}`);
1231
+ console.log();
1232
+ }
1233
+
1234
+ console.log(` ${C.bright}Summary:${C.reset}`);
1235
+ console.log(` ${C.dim} Total staked:${C.reset} ${C.bright}${(totalStaked / 1e9).toFixed(4)} AETH${C.reset} (${totalStaked.toLocaleString()} lamports)`);
1236
+ console.log(` ${C.green} ● Active:${C.reset} ${activeCount} ${C.yellow}● Deactivating:${C.reset} ${deactivatingCount} ${C.dim}● Inactive:${C.reset} ${inactiveCount}`);
1237
+ console.log();
1238
+
1239
+ } catch (err) {
1240
+ console.log(` ${C.red}✗ Failed to fetch stake positions:${C.reset} ${err.message}`);
1241
+ console.log(` ${C.dim} Is your validator running? RPC: ${rpcUrl}${C.reset}`);
1242
+ console.log(` ${C.dim} Set custom RPC: AETHER_RPC=https://your-rpc-url${C.reset}\n`);
1243
+ }
1244
+ }
1245
+
1109
1246
  // ---------------------------------------------------------------------------
1110
1247
  // UNSTAKE
1111
1248
  // Submit an Unstake transaction via POST /v1/tx to deactivate stake
@@ -1298,6 +1435,8 @@ async function walletCommand() {
1298
1435
  await balanceWallet(rl);
1299
1436
  } else if (subcmd === 'stake') {
1300
1437
  await stakeWallet(rl);
1438
+ } else if (subcmd === 'stake-positions') {
1439
+ await stakePositions(rl);
1301
1440
  } else if (subcmd === 'unstake') {
1302
1441
  await unstakeWallet(rl);
1303
1442
  } else if (subcmd === 'transfer') {
@@ -1314,6 +1453,7 @@ async function walletCommand() {
1314
1453
  console.log(` ${C.cyan}aether wallet connect${C.reset} Connect wallet via browser verification`);
1315
1454
  console.log(` ${C.cyan}aether wallet balance${C.reset} Query chain balance for an address`);
1316
1455
  console.log(` ${C.cyan}aether wallet stake${C.reset} Stake AETH to a validator`);
1456
+ console.log(` ${C.cyan}aether wallet stake-positions${C.reset} Show current stake delegations and rewards`);
1317
1457
  console.log(` ${C.cyan}aether wallet unstake${C.reset} Unstake AETH — deactivate a stake account`);
1318
1458
  console.log(` ${C.cyan}aether wallet transfer${C.reset} Transfer AETH to another address`);
1319
1459
  console.log(` ${C.cyan}aether wallet history${C.reset} Show recent transactions for an address`);
package/index.js CHANGED
@@ -187,6 +187,16 @@ const COMMANDS = {
187
187
  process.argv = originalArgv;
188
188
  },
189
189
  },
190
+ 'stake-positions': {
191
+ description: 'Show current stake positions/delegations — aether stake-positions --address <addr> [--json]',
192
+ handler: () => {
193
+ const { walletCommand } = require('./commands/wallet');
194
+ const originalArgv = process.argv;
195
+ process.argv = [...originalArgv.slice(0, 2), 'wallet', 'stake-positions', ...originalArgv.slice(3)];
196
+ walletCommand();
197
+ process.argv = originalArgv;
198
+ },
199
+ },
190
200
  unstake: {
191
201
  description: 'Unstake AETH — deactivate a stake account — aether unstake --account <stakeAcct> [--amount <aeth>]',
192
202
  handler: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
- {
1
+ {
2
2
  "name": "aether-hub",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "AeTHer Validator CLI — tiered validators (Full/Lite/Observer), system checks, onboarding, and node management",
5
5
  "main": "index.js",
6
6
  "bin": {