aether-hub 1.1.1 → 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/emergency.js +657 -0
- package/commands/init.js +1 -1
- package/commands/rewards.js +600 -600
- package/commands/validator-start.js +46 -13
- package/commands/wallet.js +308 -0
- package/index.js +28 -0
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
170
|
-
|
|
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
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
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(
|
|
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,
|
|
239
|
+
startValidatorProcess(result, finalOptions);
|
|
207
240
|
});
|
|
208
241
|
return;
|
|
209
242
|
}
|
|
210
243
|
|
|
211
|
-
startValidatorProcess(result,
|
|
244
|
+
startValidatorProcess(result, finalOptions);
|
|
212
245
|
}
|
|
213
246
|
|
|
214
247
|
/**
|
package/commands/wallet.js
CHANGED
|
@@ -1106,6 +1106,308 @@ 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
|
+
|
|
1246
|
+
// ---------------------------------------------------------------------------
|
|
1247
|
+
// UNSTAKE
|
|
1248
|
+
// Submit an Unstake transaction via POST /v1/tx to deactivate stake
|
|
1249
|
+
// ---------------------------------------------------------------------------
|
|
1250
|
+
|
|
1251
|
+
async function unstakeWallet(rl) {
|
|
1252
|
+
console.log(`\n${C.bright}${C.cyan}── Unstake AETH ──────────────────────────────────────────${C.reset}\n`);
|
|
1253
|
+
|
|
1254
|
+
const args = process.argv.slice(4);
|
|
1255
|
+
let address = null;
|
|
1256
|
+
let stakeAccount = null;
|
|
1257
|
+
let amountStr = null;
|
|
1258
|
+
|
|
1259
|
+
for (let i = 0; i < args.length; i++) {
|
|
1260
|
+
if ((args[i] === '--address' || args[i] === '-a') && args[i + 1]) {
|
|
1261
|
+
address = args[i + 1];
|
|
1262
|
+
}
|
|
1263
|
+
if ((args[i] === '--account' || args[i] === '-s') && args[i + 1]) {
|
|
1264
|
+
stakeAccount = args[i + 1];
|
|
1265
|
+
}
|
|
1266
|
+
if ((args[i] === '--amount' || args[i] === '-m') && args[i + 1]) {
|
|
1267
|
+
amountStr = args[i + 1];
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
if (!address) {
|
|
1272
|
+
const cfg = loadConfig();
|
|
1273
|
+
address = cfg.defaultWallet;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (!address) {
|
|
1277
|
+
console.log(` ${C.red}✗ No wallet address.${C.reset} Use ${C.cyan}--address <addr>${C.reset} or set a default.`);
|
|
1278
|
+
console.log(` ${C.dim}Usage: aether unstake --account <stakeAcct> [--amount <aeth>] [--address <addr>]${C.reset}\n`);
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
const wallet = loadWallet(address);
|
|
1283
|
+
if (!wallet) {
|
|
1284
|
+
console.log(` ${C.red}✗ Wallet not found:${C.reset} ${address}\n`);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Resolve stake account: --account flag, or query chain for first active stake
|
|
1289
|
+
if (!stakeAccount) {
|
|
1290
|
+
const rpcUrl = getDefaultRpc();
|
|
1291
|
+
const rawAddr = address.startsWith('ATH') ? address.slice(3) : address;
|
|
1292
|
+
|
|
1293
|
+
let stakeAccounts = [];
|
|
1294
|
+
try {
|
|
1295
|
+
const res = await httpRequest(rpcUrl, `/v1/stake?address=${encodeURIComponent(rawAddr)}`);
|
|
1296
|
+
if (res && !res.error) {
|
|
1297
|
+
stakeAccounts = Array.isArray(res) ? res : (res.accounts || []);
|
|
1298
|
+
}
|
|
1299
|
+
} catch { /* no stake accounts */ }
|
|
1300
|
+
|
|
1301
|
+
if (stakeAccounts.length === 0) {
|
|
1302
|
+
console.log(` ${C.red}✗ No active stake accounts found for this wallet.${C.reset}`);
|
|
1303
|
+
console.log(` ${C.dim}Use ${C.cyan}--account <stakeAcct>${C.reset} ${C.dim}to specify a stake account.${C.reset}`);
|
|
1304
|
+
console.log(` ${C.dim}Check delegations: aether delegations list --address ${address}${C.reset}\n`);
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Default to first active stake account
|
|
1309
|
+
const active = stakeAccounts.find(s => !s.deactivation_epoch && (s.status === 'active' || s.state === 'active'));
|
|
1310
|
+
stakeAccount = active
|
|
1311
|
+
? (active.pubkey || active.publicKey || active.account)
|
|
1312
|
+
: (stakeAccounts[0].pubkey || stakeAccounts[0].publicKey || stakeAccounts[0].account);
|
|
1313
|
+
|
|
1314
|
+
console.log(` ${C.cyan}Using stake account:${C.reset} ${C.bright}${stakeAccount}${C.reset}`);
|
|
1315
|
+
console.log(` ${C.dim}(override with ${C.cyan}--account <stakeAcct>${C.reset}${C.dim})${C.reset}\n`);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// Resolve amount: --amount flag, or prompt if partial unstake supported
|
|
1319
|
+
// If no amount provided, unstake entire stake
|
|
1320
|
+
let lamports = null;
|
|
1321
|
+
if (amountStr) {
|
|
1322
|
+
const amount = parseFloat(amountStr);
|
|
1323
|
+
if (isNaN(amount) || amount <= 0) {
|
|
1324
|
+
console.log(` ${C.red}✗ Invalid amount:${C.reset} ${amountStr}\n`);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
lamports = Math.round(amount * 1e9);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
console.log(` ${C.green}★${C.reset} Wallet: ${C.bright}${address}${C.reset}`);
|
|
1331
|
+
console.log(` ${C.green}★${C.reset} Stake acct: ${C.bright}${stakeAccount}${C.reset}`);
|
|
1332
|
+
if (lamports !== null) {
|
|
1333
|
+
console.log(` ${C.green}★${C.reset} Amount: ${C.bright}${(lamports / 1e9).toFixed(4)} AETH${C.reset} (${lamports} lamports)`);
|
|
1334
|
+
} else {
|
|
1335
|
+
console.log(` ${C.green}★${C.reset} Amount: ${C.bright}FULL STAKE${C.reset}`);
|
|
1336
|
+
}
|
|
1337
|
+
console.log();
|
|
1338
|
+
|
|
1339
|
+
// Ask for mnemonic to derive signing keypair
|
|
1340
|
+
console.log(`${C.yellow} ⚠ Signing requires your wallet passphrase.${C.reset}`);
|
|
1341
|
+
const mnemonic = await askMnemonic(rl, 'Enter your 12/24-word passphrase to sign this transaction');
|
|
1342
|
+
console.log();
|
|
1343
|
+
|
|
1344
|
+
let keyPair;
|
|
1345
|
+
try {
|
|
1346
|
+
keyPair = deriveKeypair(mnemonic, DERIVATION_PATH);
|
|
1347
|
+
} catch (e) {
|
|
1348
|
+
console.log(` ${C.red}✗ Failed to derive keypair: ${e.message}${C.reset}`);
|
|
1349
|
+
console.log(` ${C.dim}Check your passphrase and try again.${C.reset}\n`);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Verify the derived address matches the wallet
|
|
1354
|
+
const derivedAddress = formatAddress(keyPair.publicKey);
|
|
1355
|
+
if (derivedAddress !== address) {
|
|
1356
|
+
console.log(` ${C.red}✗ Passphrase mismatch.${C.reset}`);
|
|
1357
|
+
console.log(` ${C.dim} Derived: ${derivedAddress}${C.reset}`);
|
|
1358
|
+
console.log(` ${C.dim} Expected: ${address}${C.reset}\n`);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const confirm = await question(rl, ` ${C.yellow}Confirm unstake? [y/N]${C.reset} > ${C.reset}`);
|
|
1363
|
+
if (!confirm.trim().toLowerCase().startsWith('y')) {
|
|
1364
|
+
console.log(` ${C.dim}Cancelled.${C.reset}\n`);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// Build the unstake transaction
|
|
1369
|
+
const txData = {
|
|
1370
|
+
type: 'Unstake',
|
|
1371
|
+
data: {
|
|
1372
|
+
stake_account: stakeAccount,
|
|
1373
|
+
},
|
|
1374
|
+
};
|
|
1375
|
+
if (lamports !== null) {
|
|
1376
|
+
txData.data.amount = lamports;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
const tx = {
|
|
1380
|
+
signer: address.startsWith('ATH') ? address.slice(3) : address,
|
|
1381
|
+
tx_type: 'Unstake',
|
|
1382
|
+
payload: txData,
|
|
1383
|
+
fee: 0,
|
|
1384
|
+
slot: 0,
|
|
1385
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
const rpcUrl = getDefaultRpc();
|
|
1389
|
+
console.log(` ${C.dim}Submitting to ${rpcUrl}...${C.reset}`);
|
|
1390
|
+
|
|
1391
|
+
try {
|
|
1392
|
+
const result = await httpPost(rpcUrl, '/v1/tx', tx);
|
|
1393
|
+
|
|
1394
|
+
if (result.error) {
|
|
1395
|
+
console.log(`\n ${C.red}✗ Unstake failed:${C.reset} ${result.error}\n`);
|
|
1396
|
+
process.exit(1);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const sig = result.signature || result.tx_signature || result.id || JSON.stringify(result);
|
|
1400
|
+
console.log(`\n${C.green}✓ Unstake transaction submitted!${C.reset}`);
|
|
1401
|
+
console.log(` ${C.dim}Signature:${C.reset} ${sig}`);
|
|
1402
|
+
console.log(` ${C.dim}Stake will deactivate over the next epoch.${C.reset}`);
|
|
1403
|
+
console.log(` ${C.dim}Check status: aether delegations list --address ${address}${C.reset}\n`);
|
|
1404
|
+
} catch (err) {
|
|
1405
|
+
console.log(` ${C.red}✗ Failed to submit transaction:${C.reset} ${err.message}`);
|
|
1406
|
+
console.log(` ${C.dim}Is your validator running? RPC: ${rpcUrl}${C.reset}\n`);
|
|
1407
|
+
process.exit(1);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1109
1411
|
// ---------------------------------------------------------------------------
|
|
1110
1412
|
// Main dispatcher
|
|
1111
1413
|
// ---------------------------------------------------------------------------
|
|
@@ -1133,6 +1435,10 @@ async function walletCommand() {
|
|
|
1133
1435
|
await balanceWallet(rl);
|
|
1134
1436
|
} else if (subcmd === 'stake') {
|
|
1135
1437
|
await stakeWallet(rl);
|
|
1438
|
+
} else if (subcmd === 'stake-positions') {
|
|
1439
|
+
await stakePositions(rl);
|
|
1440
|
+
} else if (subcmd === 'unstake') {
|
|
1441
|
+
await unstakeWallet(rl);
|
|
1136
1442
|
} else if (subcmd === 'transfer') {
|
|
1137
1443
|
await transferWallet(rl);
|
|
1138
1444
|
} else if (subcmd === 'history' || subcmd === 'tx') {
|
|
@@ -1147,6 +1453,8 @@ async function walletCommand() {
|
|
|
1147
1453
|
console.log(` ${C.cyan}aether wallet connect${C.reset} Connect wallet via browser verification`);
|
|
1148
1454
|
console.log(` ${C.cyan}aether wallet balance${C.reset} Query chain balance for an address`);
|
|
1149
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`);
|
|
1457
|
+
console.log(` ${C.cyan}aether wallet unstake${C.reset} Unstake AETH — deactivate a stake account`);
|
|
1150
1458
|
console.log(` ${C.cyan}aether wallet transfer${C.reset} Transfer AETH to another address`);
|
|
1151
1459
|
console.log(` ${C.cyan}aether wallet history${C.reset} Show recent transactions for an address`);
|
|
1152
1460
|
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,26 @@ const COMMANDS = {
|
|
|
186
187
|
process.argv = originalArgv;
|
|
187
188
|
},
|
|
188
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
|
+
},
|
|
200
|
+
unstake: {
|
|
201
|
+
description: 'Unstake AETH — deactivate a stake account — aether unstake --account <stakeAcct> [--amount <aeth>]',
|
|
202
|
+
handler: () => {
|
|
203
|
+
const { walletCommand } = require('./commands/wallet');
|
|
204
|
+
const originalArgv = process.argv;
|
|
205
|
+
process.argv = [...originalArgv.slice(0, 2), 'wallet', 'unstake', ...originalArgv.slice(3)];
|
|
206
|
+
walletCommand();
|
|
207
|
+
process.argv = originalArgv;
|
|
208
|
+
},
|
|
209
|
+
},
|
|
189
210
|
transfer: {
|
|
190
211
|
description: 'Transfer AETH to another address — aether transfer --to <addr> --amount <aeth>',
|
|
191
212
|
handler: () => {
|
|
@@ -278,6 +299,13 @@ const COMMANDS = {
|
|
|
278
299
|
validatorsListCommand();
|
|
279
300
|
},
|
|
280
301
|
},
|
|
302
|
+
emergency: {
|
|
303
|
+
description: 'Emergency response & network alerts — status, monitor, check, alert, failover, history',
|
|
304
|
+
handler: () => {
|
|
305
|
+
const { emergencyCommand } = require('./commands/emergency');
|
|
306
|
+
emergencyCommand();
|
|
307
|
+
},
|
|
308
|
+
},
|
|
281
309
|
help: {
|
|
282
310
|
description: 'Show this help message',
|
|
283
311
|
handler: showHelp,
|
package/package.json
CHANGED