beeline-cli
Version:
A terminal wallet for the Hive blockchain - type, sign, rule the chain
364 lines (359 loc) • 16.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@oclif/core");
const neon_js_1 = require("../utils/neon.js");
const crypto_js_1 = require("../utils/crypto.js");
const hive_js_1 = require("../utils/hive.js");
const inquirer_1 = __importDefault(require("inquirer"));
class Governance extends core_1.Command {
async run() {
const { args, flags } = await this.parse(Governance);
try {
const keyManager = new crypto_js_1.KeyManager();
await keyManager.initialize();
const hiveClient = new hive_js_1.HiveClient(keyManager, flags.node);
switch (args.action) {
case 'vote':
await this.handleWitnessVote(hiveClient, keyManager, args.target, flags, true);
break;
case 'unvote':
await this.handleWitnessVote(hiveClient, keyManager, args.target, flags, false);
break;
case 'proxy':
await this.handleWitnessProxy(hiveClient, keyManager, args.target, flags, true);
break;
case 'unproxy':
await this.handleWitnessProxy(hiveClient, keyManager, '', flags, false);
break;
case 'witnesses':
await this.showWitnessList(hiveClient, flags);
break;
case 'status':
await this.showGovernanceStatus(hiveClient, keyManager, flags);
break;
default:
this.error(`Unknown action: ${args.action}`);
}
}
catch (error) {
this.error(`Governance operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
async handleWitnessVote(hiveClient, keyManager, witness, flags, approve) {
if (!witness) {
this.error(`Witness account is required for ${approve ? 'voting' : 'unvoting'}`);
}
// Clean witness name (remove @ if present)
const cleanWitness = witness.startsWith('@') ? witness.slice(1) : witness;
// Get the account to vote from
let fromAccount = flags.from;
if (!fromAccount) {
fromAccount = keyManager.getDefaultAccount();
}
if (!fromAccount) {
this.error('No account specified. Use --from or set a default account with login command.');
}
// Show operation preview
const action = approve ? 'VOTE FOR' : 'UNVOTE';
const actionColor = approve ? 'green' : 'pink';
console.log((0, neon_js_1.createNeonBox)(`
${neon_js_1.neonSymbols.vote} ${neon_js_1.neonChalk[actionColor].bold(action)} WITNESS ${neon_js_1.neonSymbols.vote}
${neon_js_1.neonChalk.cyan('From:')} ${neon_js_1.neonChalk.white(fromAccount)}
${neon_js_1.neonChalk.cyan('Witness:')} ${neon_js_1.neonChalk.white(cleanWitness)}
${neon_js_1.neonChalk.cyan('Action:')} ${neon_js_1.neonChalk[actionColor](approve ? 'Approve' : 'Disapprove')}
${flags.mock ? neon_js_1.neonChalk.yellow.bold('\n⚠️ MOCK MODE - No transaction will be broadcast') : ''}
`.trim()));
// Confirmation prompt
if (!flags.confirm && !flags.mock) {
const { proceed } = await inquirer_1.default.prompt([{
type: 'confirm',
name: 'proceed',
message: `${approve ? 'Vote for' : 'Unvote'} witness ${cleanWitness}?`,
default: false
}]);
if (!proceed) {
console.log(neon_js_1.neonChalk.yellow('Operation cancelled.'));
return;
}
}
// Handle mock case early
if (flags.mock) {
const spinner = (0, neon_js_1.neonSpinner)(`${approve ? 'Voting for' : 'Unvoting'} witness...`);
try {
// Simulate delay for mock mode
await new Promise(resolve => setTimeout(resolve, 2000));
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
console.log(neon_js_1.neonChalk.green(`✓ Mock ${approve ? 'vote' : 'unvote'} successful!`));
console.log(neon_js_1.neonChalk.white.dim(`Would ${approve ? 'vote for' : 'unvote'} witness: ${cleanWitness}`));
}
catch (error) {
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
throw error;
}
return;
}
// Check if active key exists for the account
const keys = await keyManager.listKeys(fromAccount);
const activeKey = keys.find(k => k.role === 'active');
if (!activeKey) {
console.log(neon_js_1.neonChalk.error(`${neon_js_1.neonSymbols.cross} Active key not found for account @${fromAccount}`));
console.log(neon_js_1.neonChalk.info('Import active key with: ') + neon_js_1.neonChalk.highlight(`beeline keys import ${fromAccount} active`));
return;
}
// Get PIN for transaction signing (BEFORE starting spinner)
let pin;
if (activeKey.encrypted) {
const pinPrompt = await inquirer_1.default.prompt([{
type: 'password',
name: 'pin',
message: neon_js_1.neonChalk.cyan(`Enter PIN to ${approve ? 'vote for' : 'unvote'} witness:`),
mask: '*',
validate: (input) => input.length > 0 || 'PIN required'
}]);
pin = pinPrompt.pin;
}
// NOW start spinner after all user input is complete
const spinner = (0, neon_js_1.neonSpinner)(`${approve ? 'Voting for' : 'Unvoting'} witness...`);
try {
const txId = await hiveClient.witnessVote(fromAccount, cleanWitness, approve, pin);
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
console.log(neon_js_1.neonChalk.green(`✓ Witness ${approve ? 'vote' : 'unvote'} successful!`));
console.log(neon_js_1.neonChalk.white.dim(`Transaction ID: ${txId}`));
}
catch (error) {
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
throw error;
}
}
async handleWitnessProxy(hiveClient, keyManager, proxy, flags, setProxy) {
// Get the account to set proxy from
let fromAccount = flags.from;
if (!fromAccount) {
fromAccount = keyManager.getDefaultAccount();
}
if (!fromAccount) {
this.error('No account specified. Use --from or set a default account with login command.');
}
let cleanProxy = '';
if (setProxy) {
if (!proxy) {
this.error('Proxy account is required when setting proxy');
}
cleanProxy = proxy.startsWith('@') ? proxy.slice(1) : proxy;
}
// Show operation preview
const action = setProxy ? 'SET PROXY' : 'CLEAR PROXY';
console.log((0, neon_js_1.createNeonBox)(`
${neon_js_1.neonSymbols.proxy} ${neon_js_1.neonChalk.magenta.bold(action)} ${neon_js_1.neonSymbols.proxy}
${neon_js_1.neonChalk.cyan('From:')} ${neon_js_1.neonChalk.white(fromAccount)}
${setProxy ? `${neon_js_1.neonChalk.cyan('Proxy:')} ${neon_js_1.neonChalk.white(cleanProxy)}` : neon_js_1.neonChalk.cyan('Action: Clear current proxy')}
${flags.mock ? neon_js_1.neonChalk.yellow.bold('\n⚠️ MOCK MODE - No transaction will be broadcast') : ''}
`.trim()));
// Confirmation prompt
if (!flags.confirm && !flags.mock) {
const { proceed } = await inquirer_1.default.prompt([{
type: 'confirm',
name: 'proceed',
message: setProxy ? `Set ${cleanProxy} as your witness voting proxy?` : 'Clear your current witness voting proxy?',
default: false
}]);
if (!proceed) {
console.log(neon_js_1.neonChalk.yellow('Operation cancelled.'));
return;
}
}
// Handle mock case early
if (flags.mock) {
const spinner = (0, neon_js_1.neonSpinner)(setProxy ? 'Setting proxy...' : 'Clearing proxy...');
try {
// Simulate delay for mock mode
await new Promise(resolve => setTimeout(resolve, 2000));
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
console.log(neon_js_1.neonChalk.green(`✓ Mock proxy operation successful!`));
console.log(neon_js_1.neonChalk.white.dim(setProxy ? `Would set proxy to: ${cleanProxy}` : 'Would clear current proxy'));
}
catch (error) {
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
throw error;
}
return;
}
// Check if active key exists for the account
const keys = await keyManager.listKeys(fromAccount);
const activeKey = keys.find(k => k.role === 'active');
if (!activeKey) {
console.log(neon_js_1.neonChalk.error(`${neon_js_1.neonSymbols.cross} Active key not found for account @${fromAccount}`));
console.log(neon_js_1.neonChalk.info('Import active key with: ') + neon_js_1.neonChalk.highlight(`beeline keys import ${fromAccount} active`));
return;
}
// Get PIN for transaction signing (BEFORE starting spinner)
let pin;
if (activeKey.encrypted) {
const pinPrompt = await inquirer_1.default.prompt([{
type: 'password',
name: 'pin',
message: neon_js_1.neonChalk.cyan(`Enter PIN to ${setProxy ? 'set' : 'clear'} proxy:`),
mask: '*',
validate: (input) => input.length > 0 || 'PIN required'
}]);
pin = pinPrompt.pin;
}
// NOW start spinner after all user input is complete
const spinner = (0, neon_js_1.neonSpinner)(setProxy ? 'Setting proxy...' : 'Clearing proxy...');
try {
const txId = await hiveClient.witnessProxy(fromAccount, cleanProxy, pin);
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
console.log(neon_js_1.neonChalk.green(`✓ Proxy operation successful!`));
console.log(neon_js_1.neonChalk.white.dim(`Transaction ID: ${txId}`));
}
catch (error) {
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
throw error;
}
}
async showWitnessList(hiveClient, flags) {
const spinner = (0, neon_js_1.neonSpinner)('Loading witness data...');
try {
const witnesses = await hiveClient.getWitnesses(flags.limit, flags.active);
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
console.log((0, neon_js_1.createNeonBox)(`
${neon_js_1.neonSymbols.list} ${neon_js_1.neonChalk.cyan.bold('HIVE WITNESSES')} ${neon_js_1.neonSymbols.list}
${neon_js_1.neonChalk.white.dim('Displaying top')} ${neon_js_1.neonChalk.white(witnesses.length)} ${neon_js_1.neonChalk.white.dim(flags.active ? 'active witnesses' : 'witnesses by vote count')}
`.trim()));
// Display witnesses in a formatted table
witnesses.forEach((witness, index) => {
const rank = neon_js_1.neonChalk.white.dim(`${(index + 1).toString().padStart(2, ' ')}.`);
const name = neon_js_1.neonChalk.white.bold(witness.owner.padEnd(20, ' '));
const votes = neon_js_1.neonChalk.cyan(this.formatVotes(witness.votes));
const status = witness.signing_key === 'STM1111111111111111111111111111111114T1Anm'
? neon_js_1.neonChalk.pink('DISABLED')
: neon_js_1.neonChalk.green('ACTIVE');
console.log(`${rank} ${name} ${votes} ${status}`);
});
}
catch (error) {
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
throw error;
}
}
async showGovernanceStatus(hiveClient, keyManager, flags) {
let account = flags.from;
if (!account) {
account = keyManager.getDefaultAccount();
}
if (!account) {
this.error('No account specified. Use --from or set a default account with login command.');
}
const spinner = (0, neon_js_1.neonSpinner)('Loading governance status...');
try {
const status = await hiveClient.getGovernanceStatus(account);
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
console.log((0, neon_js_1.createNeonBox)(`
${neon_js_1.neonSymbols.info} ${neon_js_1.neonChalk.cyan.bold('GOVERNANCE STATUS')} ${neon_js_1.neonSymbols.info}
${neon_js_1.neonChalk.cyan('Account:')} ${neon_js_1.neonChalk.white(account)}
${neon_js_1.neonChalk.cyan('Proxy:')} ${status.proxy || neon_js_1.neonChalk.white.dim('None')}
${neon_js_1.neonChalk.cyan('Witness Votes:')} ${neon_js_1.neonChalk.white(status.witnessVotes.length)}/30
${neon_js_1.neonChalk.cyan('Voting Power:')} ${neon_js_1.neonChalk.white(this.formatVotingPower(status.votingPower))}
${status.witnessVotes.length > 0 ? neon_js_1.neonChalk.cyan.bold('Current Witness Votes:') : ''}
${status.witnessVotes.map(w => neon_js_1.neonChalk.white(` • ${w}`)).join('\n')}
`.trim()));
}
catch (error) {
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(80) + '\r');
throw error;
}
}
formatVotes(votes) {
const voteCount = parseInt(votes);
if (voteCount >= 1000000000) {
return `${(voteCount / 1000000000).toFixed(1)}B votes`;
}
else if (voteCount >= 1000000) {
return `${(voteCount / 1000000).toFixed(1)}M votes`;
}
else if (voteCount >= 1000) {
return `${(voteCount / 1000).toFixed(1)}K votes`;
}
return `${voteCount} votes`;
}
formatVotingPower(vestsString) {
const vests = parseFloat(vestsString);
if (vests >= 1000000000) {
return `${(vests / 1000000000).toFixed(1)}B VESTS`;
}
else if (vests >= 1000000) {
return `${(vests / 1000000).toFixed(1)}M VESTS`;
}
else if (vests >= 1000) {
return `${(vests / 1000).toFixed(1)}K VESTS`;
}
return `${vests.toFixed(0)} VESTS`;
}
}
Governance.description = 'Manage Hive governance - witness voting and proxy operations with cyberpunk style';
Governance.examples = [
`$ beeline governance vote @blocktrades`,
`$ beeline governance unvote @witness`,
`$ beeline governance proxy @account`,
`$ beeline governance unproxy`,
`$ beeline governance witnesses`,
`$ beeline governance status`
];
Governance.flags = {
from: core_1.Flags.string({
char: 'f',
description: 'account to vote from (defaults to default account)'
}),
node: core_1.Flags.string({
char: 'n',
description: 'RPC node to use'
}),
confirm: core_1.Flags.boolean({
char: 'y',
description: 'skip confirmation prompt',
default: false
}),
mock: core_1.Flags.boolean({
char: 'm',
description: 'simulate governance operation without broadcasting',
default: false
}),
limit: core_1.Flags.integer({
char: 'l',
description: 'limit number of witnesses to display',
default: 30
}),
active: core_1.Flags.boolean({
char: 'a',
description: 'show only active witnesses',
default: false
})
};
Governance.args = {
action: core_1.Args.string({
description: 'governance action',
required: true,
options: ['vote', 'unvote', 'proxy', 'unproxy', 'witnesses', 'status']
}),
target: core_1.Args.string({
description: 'witness or proxy account name',
required: false
})
};
exports.default = Governance;
//# sourceMappingURL=governance.js.map