diff --git a/src/command/wallet/index.ts b/src/command/wallet/index.ts new file mode 100644 index 00000000..1f367476 --- /dev/null +++ b/src/command/wallet/index.ts @@ -0,0 +1,12 @@ +import { GroupCommand } from 'furious-commander' +import { Status } from './status' +import { WithdrawBZZ } from './withdraw-bzz' +import { WithdrawDAI } from './withdraw-dai' + +export class Wallet implements GroupCommand { + public readonly name = 'wallet' + + public readonly description = 'Manages node wallet' + + public subCommandClasses = [Status, WithdrawBZZ, WithdrawDAI] +} diff --git a/src/command/wallet/status.ts b/src/command/wallet/status.ts new file mode 100644 index 00000000..b2bda0e1 --- /dev/null +++ b/src/command/wallet/status.ts @@ -0,0 +1,20 @@ +import chalk from 'chalk' +import { LeafCommand } from 'furious-commander' +import { createKeyValue } from '../../utils/text' +import { RootCommand } from '../root-command' + +export class Status extends RootCommand implements LeafCommand { + public readonly name = 'status' + + public readonly description = `Prints node wallet balance` + + public async run(): Promise { + super.init() + + const { bzzBalance, nativeTokenBalance } = await this.bee.getWalletBalance() + + this.console.all(chalk.bold('Wallet')) + this.console.all(createKeyValue('xBZZ', bzzBalance.toDecimalString())) + this.console.all(createKeyValue('xDAI', nativeTokenBalance.toDecimalString())) + } +} diff --git a/src/command/wallet/withdraw-bzz.ts b/src/command/wallet/withdraw-bzz.ts new file mode 100644 index 00000000..fd88fabe --- /dev/null +++ b/src/command/wallet/withdraw-bzz.ts @@ -0,0 +1,52 @@ +import { BZZ } from '@ethersphere/bee-js' +import { LeafCommand, Option } from 'furious-commander' +import { createKeyValue } from '../../utils/text' +import { RootCommand } from '../root-command' + +export class WithdrawBZZ extends RootCommand implements LeafCommand { + public readonly name = 'withdraw-bzz' + + @Option({ + key: 'address', + type: 'hex-string', + description: 'Target wallet address, must be allowlisted in Bee config', + required: true, + }) + public address!: string + + @Option({ + key: 'bzz', + description: 'Amount of xBZZ to withdraw to the external wallet', + type: 'string', + required: true, + }) + public amountBzz!: string + + public readonly description = `Withdraw xBZZ to a whitelisted wallet address` + + public async run(): Promise { + super.init() + + const amount = BZZ.fromDecimalString(this.amountBzz) + + this.console.log('The address you are withdrawing to must be whitelisted in the Bee config.') + this.console.log('If you receive status code 400, the address may not be whitelisted.') + this.console.log('') + + if (!this.quiet && !this.yes) { + this.yes = await this.console.confirm( + `You are about to withdraw ${amount.toDecimalString()} xBZZ to ${ + this.address + }, are you sure you wish to proceed?`, + ) + } + + if (!this.yes && !this.quiet) { + return + } + + const transaction = await this.bee.withdrawBZZToExternalWallet(amount, this.address) + this.console.log(createKeyValue('Transaction', transaction.represent())) + this.console.log(createKeyValue('URL', `https://gnosisscan.io/tx/0x${transaction.represent()}`)) + } +} diff --git a/src/command/wallet/withdraw-dai.ts b/src/command/wallet/withdraw-dai.ts new file mode 100644 index 00000000..0b8ca0ab --- /dev/null +++ b/src/command/wallet/withdraw-dai.ts @@ -0,0 +1,52 @@ +import { DAI } from '@ethersphere/bee-js' +import { LeafCommand, Option } from 'furious-commander' +import { createKeyValue } from '../../utils/text' +import { RootCommand } from '../root-command' + +export class WithdrawDAI extends RootCommand implements LeafCommand { + public readonly name = 'withdraw-dai' + + @Option({ + key: 'address', + type: 'hex-string', + description: 'Target wallet address, must be allowlisted in Bee config', + required: true, + }) + public address!: string + + @Option({ + key: 'dai', + description: 'Amount of xDAI to withdraw to the external wallet', + type: 'string', + required: true, + }) + public amountDai!: string + + public readonly description = `Withdraw xDAI to a whitelisted wallet address` + + public async run(): Promise { + super.init() + + const amount = DAI.fromDecimalString(this.amountDai) + + this.console.log('The address you are withdrawing to must be whitelisted in the Bee config.') + this.console.log('If you receive status code 400, the address may not be whitelisted.') + this.console.log('') + + if (!this.quiet && !this.yes) { + this.yes = await this.console.confirm( + `You are about to withdraw ${amount.toDecimalString()} xDAI to ${ + this.address + }, are you sure you wish to proceed?`, + ) + } + + if (!this.yes && !this.quiet) { + return + } + + const transaction = await this.bee.withdrawDAIToExternalWallet(amount, this.address) + this.console.log(createKeyValue('Transaction', transaction.represent())) + this.console.log(createKeyValue('URL', `https://gnosisscan.io/tx/0x${transaction.represent()}`)) + } +} diff --git a/src/config.ts b/src/config.ts index 5b979b86..b1cbf7f0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -15,6 +15,7 @@ import { Stamp } from './command/stamp' import { Status } from './command/status' import { Upload } from './command/upload' import { Utility } from './command/utility' +import { Wallet } from './command/wallet' export const beeApiUrl: IOption = { key: 'bee-api-url', @@ -127,6 +128,7 @@ export const rootCommandClasses = [ Addresses, Manifest, Stake, + Wallet, Utility, Grantee, ] diff --git a/test/command/wallet.spec.ts b/test/command/wallet.spec.ts new file mode 100644 index 00000000..056f9bf8 --- /dev/null +++ b/test/command/wallet.spec.ts @@ -0,0 +1,52 @@ +import { toMatchLinesInOrder } from '../custom-matcher' +import { describeCommand, invokeTestCli } from '../utility' +import { getBeeDevOption } from '../utility/stamp' + +expect.extend({ + toMatchLinesInOrder, +}) + +describeCommand('Test Wallet command', ({ consoleMessages }) => { + test('should print wallet', async () => { + await invokeTestCli(['wallet', 'status', ...getBeeDevOption()]) + expect(consoleMessages).toMatchLinesInOrder([['Wallet'], ['xBZZ'], ['xDAI']]) + }) + + test('should withdraw xdai', async () => { + await invokeTestCli([ + 'wallet', + 'withdraw-dai', + '--address', + '00'.repeat(20), + '--dai', + '0.01', + '--yes', + ...getBeeDevOption(), + ]) + expect(consoleMessages).toMatchLinesInOrder([ + ['must be whitelisted'], + ['status code 400'], + ['Transaction'], + ['URL', 'gnosisscan'], + ]) + }) + + test('should withdraw xbzz', async () => { + await invokeTestCli([ + 'wallet', + 'withdraw-bzz', + '--address', + '00'.repeat(20), + '--bzz', + '0.01', + '--yes', + ...getBeeDevOption(), + ]) + expect(consoleMessages).toMatchLinesInOrder([ + ['must be whitelisted'], + ['status code 400'], + ['Transaction'], + ['URL', 'gnosisscan'], + ]) + }) +})