Skip to content

Commit

Permalink
feat: 'accounts balances' sub commands
Browse files Browse the repository at this point in the history
Add 'accounts balances get' and 'accounts balances send' sub-commands,
including support for stargate clients and signing clients.
  • Loading branch information
eliasmpw authored and aelesbao committed Aug 29, 2023
1 parent 1d116d1 commit 82d4ae7
Show file tree
Hide file tree
Showing 33 changed files with 1,777 additions and 1,458 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
### Features

* **cli:** add the `config` command ([#11](https://github.com/archway-network/archway-cli-v2/pull/11))
* **cli:** add the `accounts` command ([#26](https://github.com/archway-network/archway-cli-v2/pull/26),[#27](https://github.com/archway-network/archway-cli-v2/pull/27),[#28](https://github.com/archway-network/archway-cli-v2/pull/28))
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
"/oclif.manifest.json"
],
"dependencies": {
"@archwayhq/arch3.js": "^0.2.0-alpha",
"@archwayhq/arch3-core": "^0.1.0",
"@archwayhq/keyring-go": "../keyring-go",
"@cosmjs/proto-signing": "^0.30.1",
"@cosmjs/cosmwasm-stargate": "^0.28.13",
"@cosmjs/proto-signing": "^0.28.13",
"@cosmjs/stargate": "^0.28.13",
"@oclif/core": "^2.8.2",
"@oclif/plugin-help": "^5.2.9",
"@oclif/plugin-plugins": "^2.4.4",
"@webassemblyjs/wasm-opt": "^1.11.5",
"bech32": "^2.0.0",
"bip39": "^3.1.0",
"chalk": "^4.1.2",
"debug": "^4.3.4",
Expand Down
26 changes: 26 additions & 0 deletions src/arguments/amount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Args } from '@oclif/core';

import { parseAmount } from '@/utils/coin';

const AmountArgumentDescription = 'Token amount';

/**
* Check if string is a valid coin
*
* @param value - User input to be validated
* @returns The same value without any changes
*/
const validateCoin = async (value: string): Promise<string> => {
parseAmount(value);

return value;
};

/**
* Contract name argument
*/
export const amountRequired = Args.string({
required: true,
description: AmountArgumentDescription,
parse: validateCoin,
});
48 changes: 48 additions & 0 deletions src/commands/accounts/balances/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { BaseCommand } from '@/lib/base';
import { accountRequired } from '@/arguments/account';
import { Accounts } from '@/domain/Accounts';
import { KeyringFlags } from '@/flags/keyring';
import { Config } from '@/domain/Config';
import { bold, darkGreen, green, yellow } from '@/utils/style';
import { showSpinner } from '@/ui/Spinner';

import { BackendType } from '@/types/Account';

/**
* Command 'accounts balances get'
* Access the bank module to query the balance of an address or account.
*/
export default class AccountsBalancesGet extends BaseCommand<typeof AccountsBalancesGet> {
static summary = 'Query the balance of an address or account';
static args = {
account: accountRequired,
};

static flags = {
...KeyringFlags,
};

/**
* Runs the command.
*
* @returns Empty promise
*/
public async run(): Promise<void> {
const accountsDomain = await Accounts.init(this.flags['keyring-backend'] as BackendType, { filesPath: this.flags['keyring-path'] });
const config = await Config.open();

const result = await showSpinner(async () => {
const client = await config.getStargateClient();

return accountsDomain.queryBalance(client, this.args.account);
}, 'Querying balance');

if (this.jsonEnabled()) {
this.logJson(result);
} else {
this.log(`Balances for account ${green(result.account.name)} (${darkGreen(result.account.address)})\n`);
if (result.account.balances.length === 0) this.log(`- ${yellow('Empty wallet')}`);
for (const item of result.account.balances) this.log(`- ${bold(item.amount)} ${item.denom}`);
}
}
}
16 changes: 16 additions & 0 deletions src/commands/accounts/balances/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Command } from '@oclif/core';

import Help from '@/plugins/help-plugin/help';

/**
* Command 'accounts balances'
* Displays the help info for the 'accounts balances' command
*/
export default class AccountsBalances extends Command {
static summary = 'Manage the balances of an account.';

public async run(): Promise<void> {
const help = new Help(this.config, { all: true });
await help.showCommandHelp(AccountsBalances);
}
}
70 changes: 70 additions & 0 deletions src/commands/accounts/balances/send.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Flags } from '@oclif/core';

import { BaseCommand } from '@/lib/base';
import { accountRequired } from '@/arguments/account';
import { Accounts } from '@/domain/Accounts';
import { KeyringFlags } from '@/flags/keyring';
import { Config } from '@/domain/Config';
import { amountRequired } from '@/arguments/amount';
import { showSpinner } from '@/ui/Spinner';
import { bold, darkGreen, green, white } from '@/utils/style';
import { askForConfirmation } from '@/flags/force';
import { parseAmount } from '@/utils/coin';

import { AccountWithMnemonic, BackendType } from '@/types/Account';

/**
* Command 'accounts balances send'
* This command will use the bank module to send coins from one account to another.
*/
export default class AccountsBalancesSend extends BaseCommand<typeof AccountsBalancesSend> {
static summary = 'Send tokens from an address or account to another';
static args = {
account: accountRequired,
amount: amountRequired,
};

static flags = {
to: Flags.string({ description: 'Destination of the funds', required: true }),
...KeyringFlags,
};

/**
* Runs the command.
*
* @returns Empty promise
*/
public async run(): Promise<void> {
const accountsDomain = await Accounts.init(this.flags['keyring-backend'] as BackendType, { filesPath: this.flags['keyring-path'] });
const fromAccount: AccountWithMnemonic = await accountsDomain.getWithMnemonic(this.args.account);
const toAccount = await accountsDomain.accountBaseFromAddress(this.flags.to);
const config = await Config.open();
const amount = parseAmount(this.args.amount);

this.log(`Sending ${bold(amount.plainText)}`);
this.log(`From ${green(fromAccount.name)} (${darkGreen(fromAccount.address)})`);
this.log(`To ${toAccount.name ? `${green(toAccount.name)} (${darkGreen(toAccount.address)})` : green(toAccount.address)}\n`);

await askForConfirmation();
this.log();

try {
await showSpinner(async () => {
const signingClient = await config.getSigningArchwayClient(fromAccount);

fromAccount.mnemonic = '';

await signingClient.sendTokens(fromAccount.address, toAccount.address, [amount.coin], 'auto');
}, 'Sending tokens');
} catch (error: Error | any) {
if (error?.message?.toString?.()?.includes('insufficient funds'))
throw new Error(`Insufficient funds to send ${white.reset.bold(amount.plainText)} from ${green(fromAccount.name)}`);
}

this.success(
darkGreen(
`Sent ${white.reset.bold(amount.plainText)} from ${green(fromAccount.name)} to ${green(toAccount.name || toAccount.address)}`
)
);
}
}
5 changes: 1 addition & 4 deletions src/commands/accounts/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Accounts } from '@/domain/Accounts';
import { KeyringFlags } from '@/flags/keyring';

import { BackendType } from '@/types/Account';
import { NotFoundError } from '@/exceptions';

/**
* Command 'accounts get'
Expand All @@ -30,9 +29,7 @@ export default class AccountsGet extends BaseCommand<typeof AccountsGet> {
*/
public async run(): Promise<void> {
const accountsDomain = await Accounts.init(this.flags['keyring-backend'] as BackendType, { filesPath: this.flags['keyring-path'] });
const account = await accountsDomain.keystore.getPublicInfo(this.args.account);

if (!account) throw new NotFoundError('Account', this.args.account);
const account = await accountsDomain.get(this.args.account);

if (this.flags.address) {
this.log(account.address);
Expand Down
7 changes: 6 additions & 1 deletion src/commands/accounts/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Command } from '@oclif/core';

import Help from '@/plugins/help-plugin/help';

/**
* Command 'accounts'
* Displays the help info for the 'accounts' command
*/
export default class Accounts extends Command {
static summary = 'Display help for the accounts command.';
static summary = 'Manages a local keystore with wallets to sign transactions.';

public async run(): Promise<void> {
const help = new Help(this.config, { all: true });
Expand Down
4 changes: 2 additions & 2 deletions src/commands/accounts/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ export default class AccountsList extends BaseCommand<typeof AccountsList> {
const accountsDomain = await Accounts.init(this.flags['keyring-backend'] as BackendType, { filesPath: this.flags['keyring-path'] });

if (this.jsonEnabled()) {
const list = await accountsDomain.keystore.list();
const list = await accountsDomain.list();

this.logJson({ accounts: list });
} else {
const list = await accountsDomain.keystore.listNameAndAddress();
const list = await accountsDomain.listNameAndAddress();

for (const item of list) {
this.log(`${Accounts.prettyPrintNameAndAddress(item)}\n`);
Expand Down
8 changes: 4 additions & 4 deletions src/commands/accounts/remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BaseCommand } from '@/lib/base';
import { bold, darkGreen, yellow } from '@/utils/style';
import { accountRequired } from '@/arguments/account';
import { Accounts } from '@/domain/Accounts';
import { checkConfirmation, forceFlag } from '@/flags/force';
import { askForConfirmation, forceFlag } from '@/flags/force';
import { KeyringFlags } from '@/flags/keyring';

import { BackendType } from '@/types/Account';
Expand Down Expand Up @@ -36,11 +36,11 @@ export default class AccountsRemove extends BaseCommand<typeof AccountsRemove> {
accountInfo.address
)})\n`
);
await checkConfirmation(this.flags.force);
await askForConfirmation(this.flags.force);

await accountsDomain.keystore.remove(accountInfo.address);
await accountsDomain.remove(accountInfo.address);

this.log('\n');
this.log();
this.success(`${darkGreen('Account')} ${bold.green(accountInfo.name)} ${darkGreen('deleted')}`);
}
}
5 changes: 4 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Default values accross the project
*/
export const DEFAULT = {
ChainId: 'constantine-1',
ChainId: 'constantine-2',
ConfigFileName: 'modulor.json',
ContractsRelativePath: './contracts',
ContractsCargoWorkspace: 'contracts/*',
Expand All @@ -14,6 +14,8 @@ export const DEFAULT = {
WorkspaceTemplate: 'base-workspace',
// TO DO change back once templates are merged to main
TemplateBranch: 'feature/workspace-template',
GasPriceAmount: '0.005',
GasPriceDenom: 'uarch',
};

/**
Expand Down Expand Up @@ -41,4 +43,5 @@ export const ACCOUNTS = {
EntrySeparator: '<-<>->',
EntrySuffix: 'account',
TestEntrySuffix: 'test',
AddressBech32Prefix: 'archway',
};
Loading

0 comments on commit 82d4ae7

Please sign in to comment.