UNPKG

beeline-cli

Version:

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

657 lines 29.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HiveClient = void 0; exports.formatTransactionAmount = formatTransactionAmount; exports.getTransactionDescription = getTransactionDescription; const dhive_1 = require("@hiveio/dhive"); class HiveClient { constructor(keyManager, nodeUrl) { // Default to public Hive API nodes const nodes = nodeUrl ? [nodeUrl] : [ 'https://api.hive.blog', 'https://hived.emre.sh', 'https://rpc.ausbit.dev', 'https://api.openhive.network' ]; this.client = new dhive_1.Client(nodes, { timeout: 10000, failoverThreshold: 3, consoleOnFailover: false }); this.keyManager = keyManager; } async getAccount(username) { try { const accounts = await this.client.database.getAccounts([username]); if (accounts.length > 0) { const account = accounts[0]; return { name: account.name, balance: typeof account.balance === 'string' ? account.balance : account.balance.toString(), hbd_balance: typeof account.hbd_balance === 'string' ? account.hbd_balance : account.hbd_balance.toString(), vesting_shares: typeof account.vesting_shares === 'string' ? account.vesting_shares : account.vesting_shares.toString(), savings_balance: typeof account.savings_balance === 'string' ? account.savings_balance : account.savings_balance.toString(), savings_hbd_balance: typeof account.savings_hbd_balance === 'string' ? account.savings_hbd_balance : account.savings_hbd_balance.toString(), delegated_vesting_shares: typeof account.delegated_vesting_shares === 'string' ? account.delegated_vesting_shares : account.delegated_vesting_shares.toString(), received_vesting_shares: typeof account.received_vesting_shares === 'string' ? account.received_vesting_shares : account.received_vesting_shares.toString(), reward_hive_balance: typeof account.reward_hive_balance === 'string' ? account.reward_hive_balance : (account.reward_hive_balance ? account.reward_hive_balance.toString() : '0.000 HIVE'), reward_hbd_balance: typeof account.reward_hbd_balance === 'string' ? account.reward_hbd_balance : (account.reward_hbd_balance ? account.reward_hbd_balance.toString() : '0.000 HBD'), reward_vesting_balance: typeof account.reward_vesting_balance === 'string' ? account.reward_vesting_balance : (account.reward_vesting_balance ? account.reward_vesting_balance.toString() : '0.000 VESTS'), // Powerdown/withdrawal fields vesting_withdraw_rate: typeof account.vesting_withdraw_rate === 'string' ? account.vesting_withdraw_rate : (account.vesting_withdraw_rate ? account.vesting_withdraw_rate.toString() : '0.000000 VESTS'), next_vesting_withdrawal: account.next_vesting_withdrawal ? account.next_vesting_withdrawal.toString() : '1969-12-31T23:59:59', withdrawn: account.withdrawn ? parseInt(account.withdrawn.toString()) : 0, to_withdraw: account.to_withdraw ? parseInt(account.to_withdraw.toString()) : 0 }; } return null; } catch (error) { throw new Error(`Failed to fetch account: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getBalance(username) { const account = await this.getAccount(username); if (!account) { throw new Error(`Account ${username} not found`); } // Get dynamic global properties for HP calculation const globalProps = await this.client.database.getDynamicGlobalProperties(); const vestingShares = parseFloat(account.vesting_shares.toString().split(' ')[0]); const delegatedVesting = parseFloat(account.delegated_vesting_shares.toString().split(' ')[0]); const receivedVesting = parseFloat(account.received_vesting_shares.toString().split(' ')[0]); // Calculate effective HP (vesting shares + received - delegated) const effectiveVesting = vestingShares + receivedVesting - delegatedVesting; // Convert vesting shares to HIVE Power const totalVests = parseFloat(globalProps.total_vesting_shares.toString().split(' ')[0]); const totalHive = parseFloat(globalProps.total_vesting_fund_hive.toString().split(' ')[0]); const hivepower = (effectiveVesting * totalHive) / totalVests; return { hive: account.balance.toString().split(' ')[0], hbd: account.hbd_balance.toString().split(' ')[0], hp: hivepower.toFixed(3), savings_hive: account.savings_balance.toString().split(' ')[0], savings_hbd: account.savings_hbd_balance.toString().split(' ')[0] }; } async transfer(from, to, amount, currency, memo = '', pin) { try { // Get the active key for signing const privateKeyWif = await this.keyManager.getPrivateKey(from, 'active', pin); if (!privateKeyWif) { throw new Error(`Active key not found for account ${from}`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); // Create transfer operation const operation = [ 'transfer', { from, to, amount: `${amount} ${currency}`, memo } ]; // Broadcast transaction const result = await this.client.broadcast.sendOperations([operation], privateKey); // Memory scrubbing this.keyManager.scrubMemory(privateKeyWif); return result.id; } catch (error) { throw new Error(`Transfer failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async powerUp(from, to, amount, pin) { try { const privateKeyWif = await this.keyManager.getPrivateKey(from, 'active', pin); if (!privateKeyWif) { throw new Error(`Active key not found for account ${from}`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); const operation = [ 'transfer_to_vesting', { from, to, amount: `${amount} HIVE` } ]; const result = await this.client.broadcast.sendOperations([operation], privateKey); this.keyManager.scrubMemory(privateKeyWif); return result.id; } catch (error) { throw new Error(`Power up failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async powerDown(account, amount, pin) { try { const privateKeyWif = await this.keyManager.getPrivateKey(account, 'active', pin); if (!privateKeyWif) { throw new Error(`Active key not found for account ${account}`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); const operation = [ 'withdraw_vesting', { account, vesting_shares: `${amount} VESTS` } ]; const result = await this.client.broadcast.sendOperations([operation], privateKey); this.keyManager.scrubMemory(privateKeyWif); return result.id; } catch (error) { throw new Error(`Power down failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getNodeInfo() { try { const config = await this.client.database.getConfig(); const globalProps = await this.client.database.getDynamicGlobalProperties(); return { url: Array.isArray(this.client.address) ? this.client.address[0] : this.client.address, version: typeof config.HIVE_BLOCKCHAIN_VERSION === 'string' ? config.HIVE_BLOCKCHAIN_VERSION : String(config.HIVE_BLOCKCHAIN_VERSION), lastBlockNum: globalProps.head_block_number }; } catch (error) { throw new Error(`Failed to get node info: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async convertHPToVests(hp) { try { const globalProps = await this.client.database.getDynamicGlobalProperties(); const totalVests = parseFloat(globalProps.total_vesting_shares.toString().split(' ')[0]); const totalHive = parseFloat(globalProps.total_vesting_fund_hive.toString().split(' ')[0]); return (hp * totalVests) / totalHive; } catch (error) { throw new Error(`Failed to convert HP to VESTS: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async transferToSavings(from, to, amount, currency, memo = '', pin) { try { const privateKeyWif = await this.keyManager.getPrivateKey(from, 'active', pin); if (!privateKeyWif) { throw new Error(`Active key not found for account ${from}`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); const operation = [ 'transfer_to_savings', { from, to, amount: `${amount} ${currency}`, memo } ]; const result = await this.client.broadcast.sendOperations([operation], privateKey); this.keyManager.scrubMemory(privateKeyWif); return result.id; } catch (error) { throw new Error(`Transfer to savings failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async transferFromSavings(from, requestId, to, amount, currency, memo = '', pin) { try { const privateKeyWif = await this.keyManager.getPrivateKey(from, 'active', pin); if (!privateKeyWif) { throw new Error(`Active key not found for account ${from}`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); const operation = [ 'transfer_from_savings', { from, request_id: requestId, to, amount: `${amount} ${currency}`, memo } ]; const result = await this.client.broadcast.sendOperations([operation], privateKey); this.keyManager.scrubMemory(privateKeyWif); return result.id; } catch (error) { throw new Error(`Transfer from savings failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async claimRewards(account, rewardHive, rewardHbd, rewardVests, pin) { try { const privateKeyWif = await this.keyManager.getPrivateKey(account, 'posting', pin); if (!privateKeyWif) { throw new Error(`Posting key not found for account ${account}`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); const operation = [ 'claim_reward_balance', { account, reward_hive: `${rewardHive} HIVE`, reward_hbd: `${rewardHbd} HBD`, reward_vests: `${rewardVests} VESTS` } ]; const result = await this.client.broadcast.sendOperations([operation], privateKey); this.keyManager.scrubMemory(privateKeyWif); return result.id; } catch (error) { throw new Error(`Claim rewards failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getResourceCredits(username) { try { const rc = await this.client.rc.findRCAccounts([username]); if (rc.length === 0) { throw new Error(`RC data not found for ${username}`); } const current = parseInt(rc[0].rc_manabar.current_mana); const max = parseInt(rc[0].max_rc); const percentage = (current / max) * 100; return { current, max, percentage }; } catch (error) { throw new Error(`Failed to get RC data: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async broadcastCustomJson(account, id, json, requiredAuths = [], requiredPostingAuths = [], pin) { try { // Get the appropriate key based on which auth is required const keyType = requiredAuths.length > 0 ? 'active' : 'posting'; const privateKeyWif = await this.keyManager.getPrivateKey(account, keyType, pin); if (!privateKeyWif) { throw new Error(`No ${keyType} key found for @${account}. Please import your ${keyType} key first.`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); const operation = [ 'custom_json', { required_auths: requiredAuths, required_posting_auths: requiredPostingAuths, id: id, json: JSON.stringify(json) } ]; const result = await this.client.broadcast.sendOperations([operation], privateKey); return result; } catch (error) { throw new Error(`Failed to broadcast custom JSON: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Transaction History Methods async getAccountHistory(username, limit = 100, start = -1, filter) { try { let operationFilterLow = 0; let operationFilterHigh = 0; // Apply operation type filtering - but only if we have specific types // For now, let's get all transactions and filter client-side if (filter?.types && filter.types.length > 0) { const operationBits = this.getOperationBits(filter.types); operationFilterLow = operationBits.low; operationFilterHigh = operationBits.high; } // Use the simpler 3-parameter version first to debug const history = await this.client.database.call('get_account_history', [ username, start, limit ]); if (!history || !Array.isArray(history)) { throw new Error('Invalid response from API'); } const transactions = history.map((entry) => ({ trx_id: entry[1]?.trx_id || '', block: entry[1]?.block || 0, trx_in_block: entry[1]?.trx_in_block || 0, op_in_trx: entry[1]?.op_in_trx || 0, virtual_op: entry[1]?.virtual_op === true ? 1 : 0, // Handle boolean virtual_op timestamp: entry[1]?.timestamp || '', op: entry[1]?.op || ['unknown', {}] })); // Apply additional filters return this.filterTransactions(transactions, username, filter); } catch (error) { throw new Error(`Failed to fetch account history: ${error instanceof Error ? error.message : 'Unknown error'}`); } } getOperationBits(operationTypes) { const operationMap = { 'vote': 0, 'comment': 1, 'transfer': 2, 'transfer_to_vesting': 3, 'withdraw_vesting': 4, 'limit_order_create': 5, 'limit_order_cancel': 6, 'feed_publish': 7, 'convert': 8, 'account_create': 9, 'account_update': 10, 'witness_update': 11, 'account_witness_vote': 12, 'account_witness_proxy': 13, 'pow': 14, 'custom': 15, 'report_over_production': 16, 'delete_comment': 17, 'custom_json': 18, 'comment_options': 19, 'set_withdraw_vesting_route': 20, 'limit_order_create2': 21, 'claim_account': 22, 'create_claimed_account': 23, 'request_account_recovery': 24, 'recover_account': 25, 'change_recovery_account': 26, 'escrow_transfer': 27, 'escrow_dispute': 28, 'escrow_release': 29, 'pow2': 30, 'escrow_approve': 31, 'transfer_to_savings': 32, 'transfer_from_savings': 33, 'cancel_transfer_from_savings': 34, 'custom_binary': 35, 'decline_voting_rights': 36, 'reset_account': 37, 'set_reset_account': 38, 'claim_reward_balance': 39, 'delegate_vesting_shares': 40, 'account_create_with_delegation': 41, 'witness_set_properties': 42, 'account_update2': 43, 'create_proposal': 44, 'update_proposal_votes': 45, 'remove_proposal': 46, 'update_proposal': 47, 'collateralized_convert': 48, 'recurrent_transfer': 49, // Virtual operations start at higher numbers 'fill_convert_request': 50, 'author_reward': 51, 'curation_reward': 52, 'comment_reward': 53, 'liquidity_reward': 54, 'interest': 55, 'fill_vesting_withdraw': 56, 'fill_order': 57, 'shutdown_witness': 58, 'fill_transfer_from_savings': 59, 'hardfork': 60, 'comment_payout_update': 61, 'return_vesting_delegation': 62, 'comment_benefactor_reward': 63, 'producer_reward': 64 }; let low = 0; let high = 0; for (const opType of operationTypes) { const bitPosition = operationMap[opType]; if (bitPosition !== undefined) { if (bitPosition < 64) { low |= (1 << bitPosition); } else { high |= (1 << (bitPosition - 64)); } } } return { low, high }; } filterTransactions(transactions, account, filter) { if (!filter) return transactions; return transactions.filter(tx => { const opType = tx.op[0]; const opData = tx.op[1]; const txDate = new Date(tx.timestamp); // Type filter - most important for default behavior if (filter.types && filter.types.length > 0) { if (!filter.types.includes(opType)) { return false; } } // Date range filter if (filter.startDate && txDate < filter.startDate) return false; if (filter.endDate && txDate > filter.endDate) return false; // Direction filter for transfer operations if (filter.direction && ['transfer', 'transfer_to_vesting', 'transfer_to_savings', 'transfer_from_savings'].includes(opType)) { const isOutgoing = opData.from === account; const isIncoming = opData.to === account; if (filter.direction === 'incoming' && !isIncoming) return false; if (filter.direction === 'outgoing' && !isOutgoing) return false; } // Amount filter if ((filter.minAmount || filter.maxAmount) && opData.amount) { const amount = parseFloat(opData.amount.split(' ')[0]); if (filter.minAmount && amount < filter.minAmount) return false; if (filter.maxAmount && amount > filter.maxAmount) return false; } // Currency filter if (filter.currency && opData.amount) { const currency = opData.amount.split(' ')[1]; if (currency !== filter.currency) return false; } return true; }); } async getTransactionAnalytics(username, filter) { const transactions = await this.getAccountHistory(username, 1000, -1, filter); const analytics = { totalTransactions: transactions.length, totalVolume: { hive: 0, hbd: 0, vests: 0 }, totalFees: 0, averageAmount: { hive: 0, hbd: 0 }, transactionsByType: {}, transactionsByMonth: {}, topRecipients: [], topSenders: [], rewardsSummary: { author: 0, curator: 0, vesting: 0 } }; const recipients = {}; const senders = {}; let hiveTotal = 0, hbdTotal = 0, hiveCount = 0, hbdCount = 0; for (const tx of transactions) { const opType = tx.op[0]; const opData = tx.op[1]; const txDate = new Date(tx.timestamp); const monthKey = `${txDate.getFullYear()}-${String(txDate.getMonth() + 1).padStart(2, '0')}`; // Count by type analytics.transactionsByType[opType] = (analytics.transactionsByType[opType] || 0) + 1; // Count by month analytics.transactionsByMonth[monthKey] = (analytics.transactionsByMonth[monthKey] || 0) + 1; // Process transfer operations if (['transfer', 'transfer_to_vesting', 'transfer_to_savings', 'transfer_from_savings'].includes(opType) && opData.amount) { const amount = parseFloat(opData.amount.split(' ')[0]); const currency = opData.amount.split(' ')[1]; if (currency === 'HIVE') { analytics.totalVolume.hive += amount; hiveTotal += amount; hiveCount++; } else if (currency === 'HBD') { analytics.totalVolume.hbd += amount; hbdTotal += amount; hbdCount++; } else if (currency === 'VESTS') { analytics.totalVolume.vests += amount; } // Track recipients and senders if (opData.to && opData.to !== username) { recipients[opData.to] = recipients[opData.to] || { count: 0, total: 0 }; recipients[opData.to].count++; recipients[opData.to].total += amount; } if (opData.from && opData.from !== username) { senders[opData.from] = senders[opData.from] || { count: 0, total: 0 }; senders[opData.from].count++; senders[opData.from].total += amount; } } // Process reward operations if (['author_reward', 'curation_reward', 'comment_reward'].includes(opType)) { if (opType === 'author_reward' && opData.hive_payout) { analytics.rewardsSummary.author += parseFloat(opData.hive_payout.split(' ')[0]); } if (opType === 'curation_reward' && opData.reward) { analytics.rewardsSummary.curator += parseFloat(opData.reward.split(' ')[0]); } if (opData.vesting_payout) { analytics.rewardsSummary.vesting += parseFloat(opData.vesting_payout.split(' ')[0]); } } } // Calculate averages analytics.averageAmount.hive = hiveCount > 0 ? hiveTotal / hiveCount : 0; analytics.averageAmount.hbd = hbdCount > 0 ? hbdTotal / hbdCount : 0; // Sort and limit top recipients/senders analytics.topRecipients = Object.entries(recipients) .map(([account, data]) => ({ account, ...data })) .sort((a, b) => b.total - a.total) .slice(0, 10); analytics.topSenders = Object.entries(senders) .map(([account, data]) => ({ account, ...data })) .sort((a, b) => b.total - a.total) .slice(0, 10); return analytics; } // Governance operations async witnessVote(voter, witness, approve, pin) { try { const privateKeyWif = await this.keyManager.getPrivateKey(voter, 'active', pin); if (!privateKeyWif) { throw new Error(`Active key not found for account ${voter}`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); const operation = [ 'account_witness_vote', { account: voter, witness, approve } ]; const result = await this.client.broadcast.sendOperations([operation], privateKey); this.keyManager.scrubMemory(privateKeyWif); return result.id; } catch (error) { throw new Error(`Witness vote failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async witnessProxy(account, proxy, pin) { try { const privateKeyWif = await this.keyManager.getPrivateKey(account, 'active', pin); if (!privateKeyWif) { throw new Error(`Active key not found for account ${account}`); } const privateKey = dhive_1.PrivateKey.fromString(privateKeyWif); const operation = [ 'account_witness_proxy', { account, proxy } ]; const result = await this.client.broadcast.sendOperations([operation], privateKey); this.keyManager.scrubMemory(privateKeyWif); return result.id; } catch (error) { throw new Error(`Witness proxy failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getWitnesses(limit = 30, activeOnly = false) { try { const witnesses = await this.client.database.call('get_witnesses_by_vote', ['', limit]); if (activeOnly) { return witnesses.filter(w => w.signing_key !== 'STM1111111111111111111111111111111114T1Anm'); } return witnesses; } catch (error) { throw new Error(`Failed to fetch witnesses: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getGovernanceStatus(account) { try { const accountData = await this.client.database.getAccounts([account]); if (!accountData || accountData.length === 0) { throw new Error(`Account ${account} not found`); } const accountInfo = accountData[0]; return { proxy: accountInfo.proxy || null, witnessVotes: accountInfo.witness_votes || [], votingPower: (accountInfo.vesting_shares && typeof accountInfo.vesting_shares === 'string') ? accountInfo.vesting_shares : accountInfo.vesting_shares?.toString() || '0.000000 VESTS' }; } catch (error) { throw new Error(`Failed to get governance status: ${error instanceof Error ? error.message : 'Unknown error'}`); } } } exports.HiveClient = HiveClient; // Export utility functions for transaction formatting function formatTransactionAmount(amount) { const parts = amount.split(' '); const value = parseFloat(parts[0]); const currency = parts[1]; const formatted = value.toLocaleString('en-US', { minimumFractionDigits: 3, maximumFractionDigits: 3 }); return { value, currency, formatted }; } function getTransactionDescription(tx, currentUser) { const opType = tx.op[0]; const opData = tx.op[1]; switch (opType) { case 'transfer': const isOutgoing = opData.from === currentUser; const otherParty = isOutgoing ? opData.to : opData.from; const direction = isOutgoing ? 'to' : 'from'; return `Transfer ${direction} @${otherParty}`; case 'transfer_to_vesting': return opData.from === opData.to ? 'Power Up' : `Power Up to @${opData.to}`; case 'withdraw_vesting': return 'Power Down'; case 'transfer_to_savings': return 'Deposit to Savings'; case 'transfer_from_savings': return 'Withdraw from Savings'; case 'claim_reward_balance': return 'Claim Rewards'; case 'author_reward': return 'Author Reward'; case 'curation_reward': return 'Curation Reward'; case 'interest': return 'Savings Interest'; case 'fill_vesting_withdraw': return 'Power Down Payment'; default: return opType.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); } } //# sourceMappingURL=hive.js.map