UNPKG

beeline-cli

Version:

A terminal wallet for the Hive blockchain - type, sign, rule the chain

364 lines (359 loc) 16.7 kB
"use strict"; 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