aether-hub 1.1.1 → 1.1.2

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.
@@ -1106,6 +1106,171 @@ async function txHistory(rl) {
1106
1106
  }
1107
1107
  }
1108
1108
 
1109
+ // ---------------------------------------------------------------------------
1110
+ // UNSTAKE
1111
+ // Submit an Unstake transaction via POST /v1/tx to deactivate stake
1112
+ // ---------------------------------------------------------------------------
1113
+
1114
+ async function unstakeWallet(rl) {
1115
+ console.log(`\n${C.bright}${C.cyan}── Unstake AETH ──────────────────────────────────────────${C.reset}\n`);
1116
+
1117
+ const args = process.argv.slice(4);
1118
+ let address = null;
1119
+ let stakeAccount = null;
1120
+ let amountStr = null;
1121
+
1122
+ for (let i = 0; i < args.length; i++) {
1123
+ if ((args[i] === '--address' || args[i] === '-a') && args[i + 1]) {
1124
+ address = args[i + 1];
1125
+ }
1126
+ if ((args[i] === '--account' || args[i] === '-s') && args[i + 1]) {
1127
+ stakeAccount = args[i + 1];
1128
+ }
1129
+ if ((args[i] === '--amount' || args[i] === '-m') && args[i + 1]) {
1130
+ amountStr = args[i + 1];
1131
+ }
1132
+ }
1133
+
1134
+ if (!address) {
1135
+ const cfg = loadConfig();
1136
+ address = cfg.defaultWallet;
1137
+ }
1138
+
1139
+ if (!address) {
1140
+ console.log(` ${C.red}✗ No wallet address.${C.reset} Use ${C.cyan}--address <addr>${C.reset} or set a default.`);
1141
+ console.log(` ${C.dim}Usage: aether unstake --account <stakeAcct> [--amount <aeth>] [--address <addr>]${C.reset}\n`);
1142
+ return;
1143
+ }
1144
+
1145
+ const wallet = loadWallet(address);
1146
+ if (!wallet) {
1147
+ console.log(` ${C.red}✗ Wallet not found:${C.reset} ${address}\n`);
1148
+ return;
1149
+ }
1150
+
1151
+ // Resolve stake account: --account flag, or query chain for first active stake
1152
+ if (!stakeAccount) {
1153
+ const rpcUrl = getDefaultRpc();
1154
+ const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
1155
+
1156
+ let stakeAccounts = [];
1157
+ try {
1158
+ const res = await httpRequest(rpcUrl, `/v1/stake?address=${encodeURIComponent(rawAddr)}`);
1159
+ if (res && !res.error) {
1160
+ stakeAccounts = Array.isArray(res) ? res : (res.accounts || []);
1161
+ }
1162
+ } catch { /* no stake accounts */ }
1163
+
1164
+ if (stakeAccounts.length === 0) {
1165
+ console.log(` ${C.red}✗ No active stake accounts found for this wallet.${C.reset}`);
1166
+ console.log(` ${C.dim}Use ${C.cyan}--account <stakeAcct>${C.reset} ${C.dim}to specify a stake account.${C.reset}`);
1167
+ console.log(` ${C.dim}Check delegations: aether delegations list --address ${address}${C.reset}\n`);
1168
+ return;
1169
+ }
1170
+
1171
+ // Default to first active stake account
1172
+ const active = stakeAccounts.find(s => !s.deactivation_epoch && (s.status === 'active' || s.state === 'active'));
1173
+ stakeAccount = active
1174
+ ? (active.pubkey || active.publicKey || active.account)
1175
+ : (stakeAccounts[0].pubkey || stakeAccounts[0].publicKey || stakeAccounts[0].account);
1176
+
1177
+ console.log(` ${C.cyan}Using stake account:${C.reset} ${C.bright}${stakeAccount}${C.reset}`);
1178
+ console.log(` ${C.dim}(override with ${C.cyan}--account <stakeAcct>${C.reset}${C.dim})${C.reset}\n`);
1179
+ }
1180
+
1181
+ // Resolve amount: --amount flag, or prompt if partial unstake supported
1182
+ // If no amount provided, unstake entire stake
1183
+ let lamports = null;
1184
+ if (amountStr) {
1185
+ const amount = parseFloat(amountStr);
1186
+ if (isNaN(amount) || amount <= 0) {
1187
+ console.log(` ${C.red}✗ Invalid amount:${C.reset} ${amountStr}\n`);
1188
+ return;
1189
+ }
1190
+ lamports = Math.round(amount * 1e9);
1191
+ }
1192
+
1193
+ console.log(` ${C.green}★${C.reset} Wallet: ${C.bright}${address}${C.reset}`);
1194
+ console.log(` ${C.green}★${C.reset} Stake acct: ${C.bright}${stakeAccount}${C.reset}`);
1195
+ if (lamports !== null) {
1196
+ console.log(` ${C.green}★${C.reset} Amount: ${C.bright}${(lamports / 1e9).toFixed(4)} AETH${C.reset} (${lamports} lamports)`);
1197
+ } else {
1198
+ console.log(` ${C.green}★${C.reset} Amount: ${C.bright}FULL STAKE${C.reset}`);
1199
+ }
1200
+ console.log();
1201
+
1202
+ // Ask for mnemonic to derive signing keypair
1203
+ console.log(`${C.yellow} ⚠ Signing requires your wallet passphrase.${C.reset}`);
1204
+ const mnemonic = await askMnemonic(rl, 'Enter your 12/24-word passphrase to sign this transaction');
1205
+ console.log();
1206
+
1207
+ let keyPair;
1208
+ try {
1209
+ keyPair = deriveKeypair(mnemonic, DERIVATION_PATH);
1210
+ } catch (e) {
1211
+ console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}`);
1212
+ console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
1213
+ return;
1214
+ }
1215
+
1216
+ // Verify the derived address matches the wallet
1217
+ const derivedAddress = formatAddress(keyPair.publicKey);
1218
+ if (derivedAddress !== address) {
1219
+ console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
1220
+ console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
1221
+ console.log(` ${C.dim} Expected: ${address}${C.reset}\n`);
1222
+ return;
1223
+ }
1224
+
1225
+ const confirm = await question(rl, ` ${C.yellow}Confirm unstake? [y/N]${C.reset} > ${C.reset}`);
1226
+ if (!confirm.trim().toLowerCase().startsWith('y')) {
1227
+ console.log(` ${C.dim}Cancelled.${C.reset}\n`);
1228
+ return;
1229
+ }
1230
+
1231
+ // Build the unstake transaction
1232
+ const txData = {
1233
+ type: 'Unstake',
1234
+ data: {
1235
+ stake_account: stakeAccount,
1236
+ },
1237
+ };
1238
+ if (lamports !== null) {
1239
+ txData.data.amount = lamports;
1240
+ }
1241
+
1242
+ const tx = {
1243
+ signer: address.startsWith('ATH') ? address.slice(3) : address,
1244
+ tx_type: 'Unstake',
1245
+ payload: txData,
1246
+ fee: 0,
1247
+ slot: 0,
1248
+ timestamp: Math.floor(Date.now() / 1000),
1249
+ };
1250
+
1251
+ const rpcUrl = getDefaultRpc();
1252
+ console.log(` ${C.dim}Submitting to ${rpcUrl}...${C.reset}`);
1253
+
1254
+ try {
1255
+ const result = await httpPost(rpcUrl, '/v1/tx', tx);
1256
+
1257
+ if (result.error) {
1258
+ console.log(`\n ${C.red}✗ Unstake failed:${C.reset} ${result.error}\n`);
1259
+ process.exit(1);
1260
+ }
1261
+
1262
+ const sig = result.signature || result.tx_signature || result.id || JSON.stringify(result);
1263
+ console.log(`\n${C.green}✓ Unstake transaction submitted!${C.reset}`);
1264
+ console.log(` ${C.dim}Signature:${C.reset} ${sig}`);
1265
+ console.log(` ${C.dim}Stake will deactivate over the next epoch.${C.reset}`);
1266
+ console.log(` ${C.dim}Check status: aether delegations list --address ${address}${C.reset}\n`);
1267
+ } catch (err) {
1268
+ console.log(` ${C.red}✗ Failed to submit transaction:${C.reset} ${err.message}`);
1269
+ console.log(` ${C.dim}Is your validator running? RPC: ${rpcUrl}${C.reset}\n`);
1270
+ process.exit(1);
1271
+ }
1272
+ }
1273
+
1109
1274
  // ---------------------------------------------------------------------------
1110
1275
  // Main dispatcher
1111
1276
  // ---------------------------------------------------------------------------
@@ -1133,6 +1298,8 @@ async function walletCommand() {
1133
1298
  await balanceWallet(rl);
1134
1299
  } else if (subcmd === 'stake') {
1135
1300
  await stakeWallet(rl);
1301
+ } else if (subcmd === 'unstake') {
1302
+ await unstakeWallet(rl);
1136
1303
  } else if (subcmd === 'transfer') {
1137
1304
  await transferWallet(rl);
1138
1305
  } else if (subcmd === 'history' || subcmd === 'tx') {
@@ -1147,6 +1314,7 @@ async function walletCommand() {
1147
1314
  console.log(` ${C.cyan}aether wallet connect${C.reset} Connect wallet via browser verification`);
1148
1315
  console.log(` ${C.cyan}aether wallet balance${C.reset} Query chain balance for an address`);
1149
1316
  console.log(` ${C.cyan}aether wallet stake${C.reset} Stake AETH to a validator`);
1317
+ console.log(` ${C.cyan}aether wallet unstake${C.reset} Unstake AETH — deactivate a stake account`);
1150
1318
  console.log(` ${C.cyan}aether wallet transfer${C.reset} Transfer AETH to another address`);
1151
1319
  console.log(` ${C.cyan}aether wallet history${C.reset} Show recent transactions for an address`);
1152
1320
  console.log();
package/index.js CHANGED
@@ -19,6 +19,7 @@ const { networkCommand } = require('./commands/network');
19
19
  const { validatorsListCommand } = require('./commands/validators');
20
20
  const { delegationsCommand } = require('./commands/delegations');
21
21
  const { rewardsCommand } = require('./commands/rewards');
22
+ const { emergencyCommand } = require('./commands/emergency');
22
23
  const readline = require('readline');
23
24
 
24
25
  // CLI version
@@ -186,6 +187,16 @@ const COMMANDS = {
186
187
  process.argv = originalArgv;
187
188
  },
188
189
  },
190
+ unstake: {
191
+ description: 'Unstake AETH — deactivate a stake account — aether unstake --account <stakeAcct> [--amount <aeth>]',
192
+ handler: () => {
193
+ const { walletCommand } = require('./commands/wallet');
194
+ const originalArgv = process.argv;
195
+ process.argv = [...originalArgv.slice(0, 2), 'wallet', 'unstake', ...originalArgv.slice(3)];
196
+ walletCommand();
197
+ process.argv = originalArgv;
198
+ },
199
+ },
189
200
  transfer: {
190
201
  description: 'Transfer AETH to another address — aether transfer --to <addr> --amount <aeth>',
191
202
  handler: () => {
@@ -278,6 +289,13 @@ const COMMANDS = {
278
289
  validatorsListCommand();
279
290
  },
280
291
  },
292
+ emergency: {
293
+ description: 'Emergency response & network alerts — status, monitor, check, alert, failover, history',
294
+ handler: () => {
295
+ const { emergencyCommand } = require('./commands/emergency');
296
+ emergencyCommand();
297
+ },
298
+ },
281
299
  help: {
282
300
  description: 'Show this help message',
283
301
  handler: showHelp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aether-hub",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
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": {