diff --git a/src/command/grantee/create.ts b/src/command/grantee/create.ts index c65bbe24..bc4dccc8 100644 --- a/src/command/grantee/create.ts +++ b/src/command/grantee/create.ts @@ -1,8 +1,8 @@ +import fs from 'fs' import { Argument, LeafCommand, Option } from 'furious-commander' -import { GranteeCommand } from './grantee-command' import { stampProperties } from '../../utils/option' import { createKeyValue } from '../../utils/text' -import fs from 'fs' +import { GranteeCommand } from './grantee-command' export class Create extends GranteeCommand implements LeafCommand { public readonly name = 'create' @@ -25,7 +25,7 @@ export class Create extends GranteeCommand implements LeafCommand { public stamp!: string public async run(): Promise { - await super.init() + super.init() this.actReqHeaders = { 'Swarm-Act': 'true', } diff --git a/src/command/grantee/get.ts b/src/command/grantee/get.ts index ae652b1c..44dd8105 100644 --- a/src/command/grantee/get.ts +++ b/src/command/grantee/get.ts @@ -1,6 +1,6 @@ import { Argument, LeafCommand } from 'furious-commander' -import { GranteeCommand } from './grantee-command' import { createKeyValue } from '../../utils/text' +import { GranteeCommand } from './grantee-command' export class Get extends GranteeCommand implements LeafCommand { public readonly name = 'get' @@ -15,7 +15,7 @@ export class Get extends GranteeCommand implements LeafCommand { public reference!: string public async run(): Promise { - await super.init() + super.init() const response = await this.bee.getGrantees(this.reference) this.console.log( createKeyValue('Grantee public keys', response.grantees.map(grantee => grantee.toCompressedHex()).join('\n')), diff --git a/src/command/grantee/patch.ts b/src/command/grantee/patch.ts index 9fa2145b..7376ebc2 100644 --- a/src/command/grantee/patch.ts +++ b/src/command/grantee/patch.ts @@ -1,8 +1,8 @@ +import fs from 'fs' import { Argument, LeafCommand, Option } from 'furious-commander' -import { GranteeCommand } from './grantee-command' import { stampProperties } from '../../utils/option' import { createKeyValue } from '../../utils/text' -import fs from 'fs' +import { GranteeCommand } from './grantee-command' export class Patch extends GranteeCommand implements LeafCommand { public readonly name = 'patch' @@ -43,7 +43,7 @@ export class Patch extends GranteeCommand implements LeafCommand { public history!: string public async run(): Promise { - await super.init() + super.init() this.actReqHeaders = { 'Swarm-Act': 'true', 'Swarm-Act-Timestamp': Date.now().toString(), diff --git a/src/command/pinning/list.ts b/src/command/pinning/list.ts index 678f989f..6d5ef7fe 100644 --- a/src/command/pinning/list.ts +++ b/src/command/pinning/list.ts @@ -10,7 +10,7 @@ export class List extends PinningCommand implements LeafCommand { public readonly description = 'List pinned root hashes' public async run(): Promise { - await super.init() + super.init() this.console.info('Getting pinned root hashes...') const pins = await this.bee.getAllPins() diff --git a/src/command/pinning/unpin.ts b/src/command/pinning/unpin.ts index 3bb8363e..75064b5d 100644 --- a/src/command/pinning/unpin.ts +++ b/src/command/pinning/unpin.ts @@ -12,7 +12,7 @@ export class Unpin extends PinningCommand implements LeafCommand { public address!: string public async run(): Promise { - await super.init() + super.init() try { await this.bee.unpin(this.address) diff --git a/src/command/pss/receive.ts b/src/command/pss/receive.ts index 87eebb70..80159d1b 100644 --- a/src/command/pss/receive.ts +++ b/src/command/pss/receive.ts @@ -29,7 +29,7 @@ export class Receive extends PssCommand implements LeafCommand { public receivedMessage?: string public async run(): Promise { - await super.init() + super.init() this.console.log('Waiting for one PSS message on topic ' + this.topic) diff --git a/src/command/pss/send.ts b/src/command/pss/send.ts index e2e5787c..80f1107c 100644 --- a/src/command/pss/send.ts +++ b/src/command/pss/send.ts @@ -49,7 +49,7 @@ export class Send extends PssCommand implements LeafCommand { sendable?: string | Uint8Array public async run(): Promise { - await super.init() + super.init() if (this.path) { if (!fileExists(this.path)) { diff --git a/src/command/pss/subscribe.ts b/src/command/pss/subscribe.ts index 3aa13e94..e874b895 100644 --- a/src/command/pss/subscribe.ts +++ b/src/command/pss/subscribe.ts @@ -15,8 +15,8 @@ export class Subscribe extends PssCommand implements LeafCommand { }) public outFile!: string - public async run(): Promise { - await super.init() + public run(): void { + super.init() this.console.log('Subscribing for PSS messages on topic ' + this.topic) diff --git a/src/command/stake.ts b/src/command/stake/deposit.ts similarity index 74% rename from src/command/stake.ts rename to src/command/stake/deposit.ts index c50f6cba..3d3a60f2 100644 --- a/src/command/stake.ts +++ b/src/command/stake/deposit.ts @@ -1,29 +1,30 @@ import { BZZ } from '@ethersphere/bee-js' import { LeafCommand, Option } from 'furious-commander' -import { createSpinner } from '../utils/spinner' -import { createKeyValue } from '../utils/text' -import { RootCommand } from './root-command' -import { VerbosityLevel } from './root-command/command-log' +import { createSpinner } from '../../utils/spinner' +import { RootCommand } from '../root-command' +import { VerbosityLevel } from '../root-command/command-log' const MIN_DEPOSIT = BZZ.fromDecimalString('10') -export class Stake extends RootCommand implements LeafCommand { - public readonly name = 'stake' +export class Deposit extends RootCommand implements LeafCommand { + public readonly name = 'deposit' - public readonly description = `Manages nodes stake` + public readonly description = 'Stake xBZZ for the storage incentives' @Option({ - key: 'deposit', + key: 'plur', description: "Amount of PLUR to add to the node's stake", type: 'bigint', minimum: BigInt(1), + conflicts: 'bzz', }) public amountPlur!: bigint | undefined @Option({ - key: 'deposit-bzz', + key: 'bzz', description: "Amount of BZZ to add to the node's stake", type: 'string', + conflicts: 'plur', }) public amountBzz!: string | undefined @@ -36,10 +37,10 @@ export class Stake extends RootCommand implements LeafCommand { await this.deposit(BZZ.fromDecimalString(this.amountBzz)) } - const stake = await this.bee.getStake() - - this.console.log(createKeyValue('Staked xBZZ', stake.toDecimalString())) - this.console.quiet(stake.toDecimalString()) + this.console.log('Stake deposited successfully!') + this.console.log('Run `swarm-cli stake status` to check your stake status.') + this.console.log('') + this.console.log('Do note it may take a few minutes for the stake to be reflected in the node status.') } private async deposit(amount: BZZ): Promise { @@ -86,10 +87,6 @@ export class Stake extends RootCommand implements LeafCommand { try { await this.bee.depositStake(amount) spinner.stop() - - this.console.log( - 'Successfully staked! It may take a few minutes for the stake to be reflected in the node status.', - ) } catch (e) { spinner.stop() throw e diff --git a/src/command/stake/index.ts b/src/command/stake/index.ts new file mode 100644 index 00000000..3bce3063 --- /dev/null +++ b/src/command/stake/index.ts @@ -0,0 +1,13 @@ +import { GroupCommand } from 'furious-commander' +import { Deposit } from './deposit' +import { Recover } from './recover' +import { Status } from './status' +import { Withdraw } from './withdraw' + +export class Stake implements GroupCommand { + public readonly name = 'stake' + + public readonly description = 'Manages node stake' + + public subCommandClasses = [Status, Deposit, Withdraw, Recover] +} diff --git a/src/command/stake/recover.ts b/src/command/stake/recover.ts new file mode 100644 index 00000000..9421476b --- /dev/null +++ b/src/command/stake/recover.ts @@ -0,0 +1,70 @@ +import { Contract } from 'ethers' +import { Argument, LeafCommand, Option } from 'furious-commander' +import { makeReadySigner } from '../../utils/rpc' +import { RootCommand } from '../root-command' +import { createWallet } from '../utility' + +export class Recover extends RootCommand implements LeafCommand { + public readonly name = 'recover' + + public readonly description = 'Recovers xBZZ from paused staking contracts' + + @Argument({ + key: 'wallet-source', + description: 'Wallet source (path or private key string)', + required: true, + autocompletePath: true, + }) + public walletSource!: string + + @Option({ + key: 'json-rpc-url', + type: 'string', + description: 'Gnosis JSON-RPC URL', + default: 'https://xdai.fairdatasociety.org', + }) + public jsonRpcUrl!: string + + public async run(): Promise { + super.init() + + const address = '0x445B848e16730988F871c4a09aB74526d27c2Ce8' + const abi = [ + { + inputs: [], + name: 'migrateStake', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'paused', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + ] + + const wallet = await createWallet(this.walletSource, this.console) + const signer = await makeReadySigner(wallet.getPrivateKeyString(), this.jsonRpcUrl) + const contract = new Contract(address, abi, signer) + + const isPaused = await contract.paused() + + if (!isPaused) { + this.console.error('The contract is not paused. No need to recover xBZZ.') + + return + } + + this.console.log('Recovering xBZZ from paused staking contract...') + await contract.migrateStake() + } +} diff --git a/src/command/stake/status.ts b/src/command/stake/status.ts new file mode 100644 index 00000000..47d853c0 --- /dev/null +++ b/src/command/stake/status.ts @@ -0,0 +1,20 @@ +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 staked and withdrawable stake` + + public async run(): Promise { + super.init() + + const stake = await this.bee.getStake() + const surplusStake = await this.bee.getWithdrawableStake() + + this.console.log(createKeyValue('Staked xBZZ', stake.toDecimalString())) + this.console.log(createKeyValue('Withdrawable staked xBZZ', surplusStake.toDecimalString())) + this.console.quiet(stake.toDecimalString()) + } +} diff --git a/src/command/stake/withdraw.ts b/src/command/stake/withdraw.ts new file mode 100644 index 00000000..5aa5f2fc --- /dev/null +++ b/src/command/stake/withdraw.ts @@ -0,0 +1,50 @@ +import { BZZ } from '@ethersphere/bee-js' +import { LeafCommand } from 'furious-commander' +import { createSpinner } from '../../utils/spinner' +import { RootCommand } from '../root-command' +import { VerbosityLevel } from '../root-command/command-log' + +export class Withdraw extends RootCommand implements LeafCommand { + public readonly name = 'withdraw' + + public readonly description = `Withdraw surplus stake to the node's balance` + + public async run(): Promise { + super.init() + + const surplusStake = await this.bee.getWithdrawableStake() + + if (surplusStake.eq(BZZ.fromDecimalString('0'))) { + this.console.log('There is no surplus stake to withdraw.') + + return + } + + if (!this.quiet && !this.yes) { + this.yes = await this.console.confirm( + `You are about to withdraw a surplus stake of ${surplusStake.toDecimalString()} xBZZ, are you sure you wish to proceed?`, + ) + } + + if (!this.yes && !this.quiet) { + return + } + + const spinner = createSpinner('Withdrawing surplus stake') + + if (this.verbosity !== VerbosityLevel.Quiet && !this.curl) { + spinner.start() + } + try { + await this.bee.withdrawSurplusStake() + spinner.stop() + + this.console.log( + 'Successfully withdrawn surplus stake! It may take a few minutes for the withdrawal to be reflected in the node status.', + ) + } catch (e) { + spinner.stop() + throw e + } + } +} diff --git a/src/command/stamp/dilute.ts b/src/command/stamp/dilute.ts index 88e3e3ff..dc5cfbb0 100644 --- a/src/command/stamp/dilute.ts +++ b/src/command/stamp/dilute.ts @@ -25,7 +25,7 @@ export class Dilute extends StampCommand implements LeafCommand { public stamp!: string public async run(): Promise { - await super.init() + super.init() if (!this.stamp) { this.stamp = await pickStamp(this.bee, this.console) diff --git a/src/command/stamp/list.ts b/src/command/stamp/list.ts index 0fcbfb2b..f6a69888 100644 --- a/src/command/stamp/list.ts +++ b/src/command/stamp/list.ts @@ -43,7 +43,7 @@ export class List extends StampCommand implements LeafCommand { public minUsage!: number public async run(): Promise { - await super.init() + super.init() this.console.verbose(`Listing postage stamps...`) const stamps = (await this.bee.getAllPostageBatch()) || [] diff --git a/src/command/stamp/show.ts b/src/command/stamp/show.ts index f157440a..f09b9669 100644 --- a/src/command/stamp/show.ts +++ b/src/command/stamp/show.ts @@ -12,7 +12,7 @@ export class Show extends StampCommand implements LeafCommand { public stamp!: string public async run(): Promise { - await super.init() + super.init() if (!this.stamp) { this.stamp = await pickStamp(this.bee, this.console) diff --git a/src/command/stamp/topup.ts b/src/command/stamp/topup.ts index f169a3e0..c7245d4c 100644 --- a/src/command/stamp/topup.ts +++ b/src/command/stamp/topup.ts @@ -23,7 +23,7 @@ export class Topup extends StampCommand implements LeafCommand { public stamp!: string public async run(): Promise { - await super.init() + super.init() if (!this.stamp) { this.stamp = await pickStamp(this.bee, this.console) diff --git a/src/command/status.ts b/src/command/status.ts index 453b42dc..bf89ba54 100644 --- a/src/command/status.ts +++ b/src/command/status.ts @@ -65,14 +65,14 @@ export class Status extends RootCommand implements LeafCommand { this.console.all(createKeyValue('Total xBZZ', totalBalance.toDecimalString())) } - if (nodeInfo.beeMode !== BeeModes.ULTRA_LIGHT && nodeInfo.beeMode !== BeeModes.DEV) { + if (nodeInfo.beeMode === BeeModes.FULL) { this.console.all('') this.console.all(chalk.bold('Staking')) const stake = await this.bee.getStake() + const surplusStake = await this.bee.getWithdrawableStake() this.console.all(createKeyValue('Staked xBZZ', stake.toDecimalString())) - } + this.console.all(createKeyValue('Withdrawable staked xBZZ', surplusStake.toDecimalString())) - if (nodeInfo.beeMode === BeeModes.FULL) { const reserveStatus = await this.bee.getStatus() this.console.all('') this.console.all(chalk.bold('Reserve')) diff --git a/src/command/utility/lock.ts b/src/command/utility/lock.ts index 35e8be97..c783f50c 100644 --- a/src/command/utility/lock.ts +++ b/src/command/utility/lock.ts @@ -17,7 +17,7 @@ export class Lock extends RootCommand implements LeafCommand { public walletSource!: string public async run(): Promise { - await super.init() + super.init() const wallet = await createWallet(this.walletSource, this.console) const password = await this.console.askForPasswordWithConfirmation( 'Enter a new password to encrypt key file', diff --git a/src/command/utility/unlock.ts b/src/command/utility/unlock.ts index 587a54ed..31ef6f6d 100644 --- a/src/command/utility/unlock.ts +++ b/src/command/utility/unlock.ts @@ -16,7 +16,7 @@ export class Unlock extends RootCommand implements LeafCommand { public walletSource!: string public async run(): Promise { - await super.init() + super.init() const wallet = await createWallet(this.walletSource, this.console) if (!this.yes) { diff --git a/src/config.ts b/src/config.ts index 12a4b32d..5b979b86 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ import { Addresses } from './command/addresses' import { Cheque } from './command/cheque' import { Download } from './command/download' import { Feed } from './command/feed' +import { Grantee } from './command/grantee' import { Hash } from './command/hash' import { Identity } from './command/identity' import { Manifest } from './command/manifest' @@ -14,7 +15,6 @@ import { Stamp } from './command/stamp' import { Status } from './command/status' import { Upload } from './command/upload' import { Utility } from './command/utility' -import { Grantee } from './command/grantee' export const beeApiUrl: IOption = { key: 'bee-api-url', diff --git a/test/command/stake.spec.ts b/test/command/stake.spec.ts index 409062b1..7c8edbd0 100644 --- a/test/command/stake.spec.ts +++ b/test/command/stake.spec.ts @@ -8,16 +8,14 @@ expect.extend({ describeCommand('Test Stake command', ({ consoleMessages }) => { test('should stake with bzz, plur, and print stake', async () => { - await invokeTestCli(['stake', ...getBeeDevOption()]) - await invokeTestCli(['stake', '--deposit-bzz', '10', '--yes', ...getBeeDevOption()]) - await invokeTestCli(['stake', '--deposit', '10', '--yes', ...getBeeDevOption()]) - await invokeTestCli(['stake', ...getBeeDevOption()]) + await invokeTestCli(['stake', 'status', ...getBeeDevOption()]) + await invokeTestCli(['stake', 'deposit', '--bzz', '10', '--yes', ...getBeeDevOption()]) + await invokeTestCli(['stake', 'deposit', '--plur', '10', '--yes', ...getBeeDevOption()]) + await invokeTestCli(['stake', 'status', ...getBeeDevOption()]) expect(consoleMessages).toMatchLinesInOrder([ ['Staked xBZZ', '0.0000000000000000'], - ['Successfully staked!'], - ['Staked xBZZ', '10.0000000000000000'], - ['Successfully staked!'], - ['Staked xBZZ', '10.0000000000000010'], + ['Stake deposited successfully!'], + ['Stake deposited successfully!'], ['Staked xBZZ', '10.0000000000000010'], ]) })