diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a554011..9c5538e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ Version header format: `[version] Name - year-month-day (entropy-core compatibil - new: './src/balance' - new file structure for our CLI/TUI flows - new: './src/balance/command.ts' - main entry file for balance command for tui/cli - new: './src/balance/utils.ts' - utilities and helper methods for all things balance +- new: './src/transfer' - new file structure for our CLI/TUI flows + - new: './src/transfer/command.ts' - main entry file for transfer command for tui/cli + - new: './src/transfer/utils.ts' - utilities and helper methods for all things transfer ### Changed @@ -39,6 +42,7 @@ Version header format: `[version] Name - year-month-day (entropy-core compatibil - logger to handle nested contexts for better organization of logs - merged user + dev program folders + tests - removed flows/balance/*.ts directory with file restructure +- removed flows/entropyTransfer/*.ts directory with file restructure ### Broke diff --git a/src/cli.ts b/src/cli.ts index 5af9da0a..6e1e9bc2 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -7,12 +7,12 @@ import * as config from './config' import { EntropyTuiOptions } from './types' import { cliListAccounts } from './flows/manage-accounts/cli' -import { cliEntropyTransfer } from './flows/entropyTransfer/cli' import { cliSign } from './flows/sign/cli' import { getSelectedAccount, stringify } from './common/utils' import Entropy from '@entropyxyz/sdk' import { initializeEntropy } from './common/initializeEntropy' import { BalanceCommand } from './balance/command' +import { TransferCommand } from './transfer/command' const program = new Command() // Array of restructured commands to make it easier to migrate them to the new "flow" @@ -70,7 +70,7 @@ function currentAccountAddressOption () { let entropy: Entropy -async function loadEntropy (address: string, endpoint: string, password: string) { +export async function loadEntropy (address: string, endpoint: string, password?: string): Promise { const storedConfig = config.getSync() const selectedAccount = getSelectedAccount(storedConfig.accounts, address) @@ -82,6 +82,12 @@ async function loadEntropy (address: string, endpoint: string, password: string) } entropy = await initializeEntropy({ keyMaterial: selectedAccount.data, endpoint, password }) + + if (!entropy?.keyring?.accounts?.registration?.pair) { + throw new Error("Signer keypair is undefined or not properly initialized.") + } + + return entropy } /* no command */ @@ -147,8 +153,9 @@ program.command('transfer') .addOption(passwordOption('Password for the source account (if required)')) .addOption(endpointOption()) .addOption(currentAccountAddressOption()) - .action(async (source, destination, amount, opts) => { - await cliEntropyTransfer({ source, destination, amount, ...opts }) + .action(async (_source, destination, amount, opts) => { + const transferCommand = new TransferCommand(entropy, opts.endpoint) + await transferCommand.sendTransfer(destination, amount) // writeOut(??) // TODO: write the output process.exit(0) }) diff --git a/src/common/progress.ts b/src/common/progress.ts index a2410501..bbdd6564 100644 --- a/src/common/progress.ts +++ b/src/common/progress.ts @@ -11,10 +11,10 @@ export function setupProgress (label: string): { start: () => void; stop: () => }) const start = () => { - // 160 was found through trial and error, don't believe there is a formula to + // 150 was found through trial and error, don't believe there is a formula to // determine the exact time it takes for the transaction to be processed and finalized // TO-DO: Change progress bar to loading animation? - b1.start(160, 0, { + b1.start(150, 0, { speed: "N/A" }) // update values diff --git a/src/flows/entropyTransfer/cli.ts b/src/flows/entropyTransfer/cli.ts deleted file mode 100644 index 1a0abe51..00000000 --- a/src/flows/entropyTransfer/cli.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { initializeEntropy } from '../../common/initializeEntropy' -import * as config from '../../config' -import { formatAmountAsHex } from '../../common/utils' -import { EntropyLogger } from 'src/common/logger' - -export async function cliEntropyTransfer ({ source, password, destination, amount, endpoint }) { - const logger = new EntropyLogger('CLI::TRANSFER', endpoint) - // NOTE: password is optional, is only for source account (if that is encrypted) - - const storedConfig = await config.get() - const account = storedConfig.accounts.find(account => account.address === source) - if (!account) throw Error(`No account with address ${source}`) - // QUESTION: is throwing the right response? - logger.debug('account', account) - - const entropy = await initializeEntropy({ keyMaterial: account.data, password, endpoint }) - - if (!entropy?.registrationManager?.signer?.pair) { - throw new Error("Signer keypair is undefined or not properly initialized.") - } - const formattedAmount = formatAmountAsHex(amount) - const tx = await entropy.substrate.tx.balances.transferAllowDeath( - destination, - BigInt(formattedAmount), - // WARNING: this is moving ... a lot? What? - ) - - await tx.signAndSend (entropy.registrationManager.signer.pair, ({ status }) => { - logger.debug('signAndSend status:') - logger.debug(status) - }) -} diff --git a/src/flows/entropyTransfer/index.ts b/src/flows/entropyTransfer/index.ts deleted file mode 100644 index 43f5532e..00000000 --- a/src/flows/entropyTransfer/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -import inquirer from "inquirer" -import { getSelectedAccount, print } from "../../common/utils" -import { initializeEntropy } from "../../common/initializeEntropy" -import { transfer } from "./transfer" -import { setupProgress } from "src/common/progress" - -const question = [ - { - type: "input", - name: "amount", - message: "Input amount to transfer:", - default: "1", - validate: (amount) => { - if (isNaN(amount) || parseInt(amount) <= 0) { - return 'Please enter a value greater than 0' - } - return true - } - }, - { - type: "input", - name: "recipientAddress", - message: "Input recipient's address:", - }, -] - -export async function entropyTransfer ({ accounts, selectedAccount: selectedAccountAddress }, options) { - const { endpoint } = options - const selectedAccount = getSelectedAccount(accounts, selectedAccountAddress) - - const { start: startProgress, stop: stopProgress } = setupProgress('Transferring Funds') - - try { - const entropy = await initializeEntropy({ - keyMaterial: selectedAccount.data, - endpoint - }) - - const { amount, recipientAddress } = await inquirer.prompt(question) - - if (!entropy?.keyring?.accounts?.registration?.pair) { - throw new Error("Signer keypair is undefined or not properly initialized.") - } - const formattedAmount = BigInt(parseInt(amount) * 1e10) - startProgress() - const transferStatus = await transfer( - entropy, - { - from: entropy.keyring.accounts.registration.pair, - to: recipientAddress, - amount: formattedAmount - } - ) - if (transferStatus.isFinalized) stopProgress() - - print('') - print(`Transaction successful: Sent ${amount} to ${recipientAddress}`) - print('') - print('Press enter to return to main menu') - } catch (error) { - stopProgress() - console.error('ERR:::', error); - } -} diff --git a/src/flows/index.ts b/src/flows/index.ts index 1aac99a8..474d6823 100644 --- a/src/flows/index.ts +++ b/src/flows/index.ts @@ -2,5 +2,4 @@ export { entropyFaucet } from './entropyFaucet' export { entropyRegister } from './register' export { userPrograms, devPrograms } from './programs' export { sign } from './sign' -export { entropyTransfer } from './entropyTransfer' export { manageAccounts } from './manage-accounts' diff --git a/src/transfer/command.ts b/src/transfer/command.ts new file mode 100644 index 00000000..7fee0ff1 --- /dev/null +++ b/src/transfer/command.ts @@ -0,0 +1,51 @@ +import Entropy from "@entropyxyz/sdk"; +import { BaseCommand } from "../common/base-command"; +import { setupProgress } from "../common/progress"; +import * as TransferUtils from './utils' +import inquirer from "inquirer"; + +const FLOW_CONTEXT = 'ENTROPY_TRANSFER' +const question = [ + { + type: "input", + name: "amount", + message: "Input amount to transfer:", + default: "1", + validate: (amount) => { + if (isNaN(amount) || parseInt(amount) <= 0) { + return 'Please enter a value greater than 0' + } + return true + } + }, + { + type: "input", + name: "recipientAddress", + message: "Input recipient's address:", + }, +] + +export class TransferCommand extends BaseCommand { + constructor (entropy: Entropy, endpoint: string) { + super(entropy, endpoint, FLOW_CONTEXT) + } + + public async askQuestions () { + return inquirer.prompt(question) + } + + public async sendTransfer (toAddress: string, amount: string) { + const { start: startProgress, stop: stopProgress } = setupProgress('Transferring Funds') + + const formattedAmount = BigInt(parseInt(amount) * 1e10) + startProgress() + try { + const transferStatus = await TransferUtils.transfer(this.entropy, { from: this.entropy.keyring.accounts.registration.pair, to: toAddress, amount: formattedAmount }) + if (transferStatus.isFinalized) return stopProgress() + } catch (error) { + this.logger.error('There was an issue sending this transfer', error) + stopProgress() + throw error + } + } +} \ No newline at end of file diff --git a/src/flows/entropyTransfer/types.ts b/src/transfer/types.ts similarity index 99% rename from src/flows/entropyTransfer/types.ts rename to src/transfer/types.ts index c23b6b77..1ceaf8b2 100644 --- a/src/flows/entropyTransfer/types.ts +++ b/src/transfer/types.ts @@ -1,5 +1,6 @@ // @ts-ignore import { Pair } from '@entropyxyz/sdk/keys' + export interface TransferOptions { from: Pair to: string diff --git a/src/flows/entropyTransfer/transfer.ts b/src/transfer/utils.ts similarity index 100% rename from src/flows/entropyTransfer/transfer.ts rename to src/transfer/utils.ts diff --git a/src/tui.ts b/src/tui.ts index e22f71ac..4db275f4 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -7,6 +7,8 @@ import { logo } from './common/ascii' import { print } from './common/utils' import { EntropyLogger } from './common/logger' import { BalanceCommand } from './balance/command' +import { TransferCommand } from './transfer/command' +import { loadEntropy } from './cli' let shouldInit = true @@ -23,7 +25,7 @@ export default function tui (entropy: Entropy, options: EntropyTuiOptions) { 'Balance': () => {}, 'Register': flows.entropyRegister, 'Sign': flows.sign, - 'Transfer': flows.entropyTransfer, + 'Transfer': () => {}, // TODO: design programs in TUI (merge deploy+user programs) 'Deploy Program': flows.devPrograms, 'User Programs': flows.userPrograms, @@ -57,6 +59,11 @@ async function main (entropy: Entropy, choices, options, logger: EntropyLogger) storedConfig = await config.get() } + // If the selected account changes within the TUI we need to reset the entropy instance being used + if (storedConfig.selectedAccount !== entropy.keyring.accounts.registration.address) { + entropy = await loadEntropy(storedConfig.selectedAccount, options.endpoint) + } + const answers = await inquirer.prompt([{ type: 'list', name: 'choice', @@ -78,11 +85,29 @@ async function main (entropy: Entropy, choices, options, logger: EntropyLogger) logger.debug(answers) switch (answers.choice) { case "Balance": { - const balanceCommand = new BalanceCommand(entropy, options.endpoint) - const balanceString = await balanceCommand.getBalance(storedConfig.selectedAccount) - print(`Address ${storedConfig.selectedAccount} has a balance of: ${balanceString}`) + try { + const balanceCommand = new BalanceCommand(entropy, options.endpoint) + const balanceString = await balanceCommand.getBalance(storedConfig.selectedAccount) + print(`Address ${storedConfig.selectedAccount} has a balance of: ${balanceString}`) + } catch (error) { + console.error('There was an error retrieving balance', error) + } break; } + case "Transfer": { + try { + const transferCommand = new TransferCommand(entropy, options.endpoint) + const { amount, recipientAddress } = await transferCommand.askQuestions() + await transferCommand.sendTransfer(recipientAddress, amount) + print('') + print(`Transaction successful: Sent ${amount} to ${recipientAddress}`) + print('') + print('Press enter to return to main menu') + } catch (error) { + console.error('There was an error sending the transfer', error) + } + break + } default: { const newConfigUpdates = await choices[answers.choice](storedConfig, options, logger) if (typeof newConfigUpdates === 'string' && newConfigUpdates === 'exit') { diff --git a/tests/transfer.test.ts b/tests/transfer.test.ts index d806403b..12c9736c 100644 --- a/tests/transfer.test.ts +++ b/tests/transfer.test.ts @@ -11,8 +11,8 @@ import { } from './testing-utils' import { initializeEntropy } from '../src/common/initializeEntropy' -import { transfer } from '../src/flows/entropyTransfer/transfer' import * as BalanceUtils from '../src/balance/utils' +import * as TransferUtils from '../src/transfer/utils' import { charlieStashAddress, charlieStashSeed } from './testing-utils/constants' const networkType = 'two-nodes' @@ -62,7 +62,7 @@ test('Transfer', async (t) => { const transferStatus = await run( 'transfer', - transfer(entropy, { + TransferUtils.transfer(entropy, { from: charlieEntropy.keyring.accounts.registration.pair, to: recipientAddress, amount: BigInt(1000 * 10e10)