|
@@ -198,6 +198,14 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
|
|
|
return this.keyring.getPair(key) as NamedKeyringPair
|
|
|
}
|
|
|
|
|
|
+ getPairByName(name: string): NamedKeyringPair {
|
|
|
+ const pair = this.getPairs().find((p) => this.getAccountFileName(p.meta.name) === this.getAccountFileName(name))
|
|
|
+ if (!pair) {
|
|
|
+ throw new CLIError(`Account not found by name: ${name}`)
|
|
|
+ }
|
|
|
+ return pair
|
|
|
+ }
|
|
|
+
|
|
|
async getDecodedPair(key: string | AccountId): Promise<NamedKeyringPair> {
|
|
|
const pair = this.getPair(key.toString())
|
|
|
|
|
@@ -277,7 +285,7 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
|
|
|
|
|
|
const longestNameLen: number = pairs.reduce((prev, curr) => Math.max(curr.meta.name.length, prev), 0)
|
|
|
const nameColLength: number = Math.min(longestNameLen + 1, 20)
|
|
|
- const chosenKey = await this.simplePrompt({
|
|
|
+ const chosenKey = await this.simplePrompt<string>({
|
|
|
message,
|
|
|
type: 'list',
|
|
|
choices: pairs.map((p, i) => ({
|
|
@@ -320,97 +328,109 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async promptForStakingAccount(stakeValue: BN, memberId: MemberId, member: Membership): Promise<string> {
|
|
|
- this.log(`Required stake: ${formatBalance(stakeValue)}`)
|
|
|
- let stakingAccount: string
|
|
|
- while (true) {
|
|
|
- stakingAccount = await this.promptForAnyAddress('Choose staking account')
|
|
|
- const { balances } = await this.getApi().getAccountSummary(stakingAccount)
|
|
|
- const stakingStatus = await this.getApi().stakingAccountStatus(stakingAccount)
|
|
|
+ async setupStakingAccount(
|
|
|
+ memberId: MemberId,
|
|
|
+ member: Membership,
|
|
|
+ address?: string,
|
|
|
+ requiredStake: BN = new BN(0),
|
|
|
+ fundsSource?: string
|
|
|
+ ): Promise<string> {
|
|
|
+ if (fundsSource && !this.isKeyAvailable(fundsSource)) {
|
|
|
+ throw new CLIError(`Key ${chalk.magentaBright(fundsSource)} is not available!`)
|
|
|
+ }
|
|
|
|
|
|
- if (balances.lockedBalance.gtn(0)) {
|
|
|
- this.warn('This account is already used for other staking purposes, choose different account...')
|
|
|
- continue
|
|
|
- }
|
|
|
+ if (!address) {
|
|
|
+ address = await this.promptForAnyAddress('Choose staking account')
|
|
|
+ }
|
|
|
+ const { balances } = await this.getApi().getAccountSummary(address)
|
|
|
+ const stakingStatus = await this.getApi().stakingAccountStatus(address)
|
|
|
|
|
|
- if (stakingStatus && !stakingStatus.member_id.eq(memberId)) {
|
|
|
- this.warn('This account is already used as staking accout by other member, choose different account...')
|
|
|
- continue
|
|
|
- }
|
|
|
+ if (balances.lockedBalance.gtn(0)) {
|
|
|
+ throw new CLIError('This account is already used for other staking purposes, choose a different account...')
|
|
|
+ }
|
|
|
|
|
|
- let additionalStakingAccountCosts = new BN(0)
|
|
|
- if (!stakingStatus || (stakingStatus && stakingStatus.confirmed.isFalse)) {
|
|
|
- if (!this.isKeyAvailable(stakingAccount)) {
|
|
|
- this.warn(
|
|
|
- 'Account is not a confirmed staking account and cannot be directly accessed via CLI, choose different account...'
|
|
|
- )
|
|
|
- continue
|
|
|
- }
|
|
|
- this.warn(
|
|
|
- `This account is not a confirmed staking account. ` +
|
|
|
- `Additional funds (fees) may be required to set it as a staking account.`
|
|
|
- )
|
|
|
- if (!stakingStatus) {
|
|
|
- additionalStakingAccountCosts = await this.getApi().estimateFee(
|
|
|
- await this.getDecodedPair(stakingAccount),
|
|
|
- this.getOriginalApi().tx.members.addStakingAccountCandidate(memberId)
|
|
|
- )
|
|
|
- additionalStakingAccountCosts = additionalStakingAccountCosts.add(STAKING_ACCOUNT_CANDIDATE_STAKE)
|
|
|
- }
|
|
|
- }
|
|
|
+ if (stakingStatus && !stakingStatus.member_id.eq(memberId)) {
|
|
|
+ throw new CLIError(
|
|
|
+ 'This account is already used as staking accout by other member, choose a different account...'
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- const requiredStakingAccountBalance = stakeValue.add(additionalStakingAccountCosts)
|
|
|
- const missingStakingAccountBalance = requiredStakingAccountBalance.sub(balances.availableBalance)
|
|
|
- if (missingStakingAccountBalance.gtn(0)) {
|
|
|
- this.warn(
|
|
|
- `Not enough available staking account balance! Missing: ${chalk.cyan(
|
|
|
- formatBalance(missingStakingAccountBalance)
|
|
|
- )}.` +
|
|
|
- (additionalStakingAccountCosts.gtn(0)
|
|
|
- ? ` (includes ${formatBalance(
|
|
|
- additionalStakingAccountCosts
|
|
|
- )} which is a required fee and candidate stake for adding a new staking account)`
|
|
|
- : '')
|
|
|
+ let candidateTxFee = new BN(0)
|
|
|
+ if (!stakingStatus || (stakingStatus && stakingStatus.confirmed.isFalse)) {
|
|
|
+ if (!this.isKeyAvailable(address)) {
|
|
|
+ throw new CLIError(
|
|
|
+ 'Account is not a confirmed staking account and cannot be directly accessed via CLI, choose different account...'
|
|
|
)
|
|
|
- const transferTokens = await this.simplePrompt({
|
|
|
- type: 'confirm',
|
|
|
- message: `Do you want to transfer ${chalk.cyan(
|
|
|
- formatBalance(missingStakingAccountBalance)
|
|
|
- )} from another account?`,
|
|
|
- })
|
|
|
- if (transferTokens) {
|
|
|
- const key = await this.promptForAccount('Choose source account')
|
|
|
- await this.sendAndFollowNamedTx(await this.getDecodedPair(key), 'balances', 'transferKeepAlive', [
|
|
|
- stakingAccount,
|
|
|
- missingStakingAccountBalance,
|
|
|
- ])
|
|
|
- } else {
|
|
|
- continue
|
|
|
- }
|
|
|
}
|
|
|
-
|
|
|
+ this.warn(
|
|
|
+ `This account is not a confirmed staking account. ` +
|
|
|
+ `Additional funds (fees) may be required to set it as a staking account.`
|
|
|
+ )
|
|
|
if (!stakingStatus) {
|
|
|
- await this.sendAndFollowNamedTx(
|
|
|
- await this.getDecodedPair(stakingAccount),
|
|
|
- 'members',
|
|
|
- 'addStakingAccountCandidate',
|
|
|
- [memberId]
|
|
|
+ candidateTxFee = await this.getApi().estimateFee(
|
|
|
+ await this.getDecodedPair(address),
|
|
|
+ this.getOriginalApi().tx.members.addStakingAccountCandidate(memberId)
|
|
|
)
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (!stakingStatus || stakingStatus.confirmed.isFalse) {
|
|
|
- await this.sendAndFollowNamedTx(
|
|
|
- await this.getDecodedPair(member.controller_account.toString()),
|
|
|
- 'members',
|
|
|
- 'confirmStakingAccount',
|
|
|
- [memberId, stakingAccount]
|
|
|
- )
|
|
|
+ const requiredStakingAccountBalance = requiredStake.add(candidateTxFee).add(STAKING_ACCOUNT_CANDIDATE_STAKE)
|
|
|
+ const missingStakingAccountBalance = requiredStakingAccountBalance.sub(balances.availableBalance)
|
|
|
+ if (missingStakingAccountBalance.gtn(0)) {
|
|
|
+ this.warn(
|
|
|
+ `Not enough available staking account balance! Missing: ${chalk.cyanBright(
|
|
|
+ formatBalance(candidateTxFee.add(STAKING_ACCOUNT_CANDIDATE_STAKE))
|
|
|
+ )}. (includes ${chalk.cyanBright(formatBalance(candidateTxFee))} transaction fee and ${chalk.cyanBright(
|
|
|
+ formatBalance(STAKING_ACCOUNT_CANDIDATE_STAKE)
|
|
|
+ )} staking account candidate stake)`
|
|
|
+ )
|
|
|
+ const transferTokens = await this.requestConfirmation(
|
|
|
+ `Do you want to transfer ${chalk.cyan(formatBalance(missingStakingAccountBalance))} from another account?`
|
|
|
+ )
|
|
|
+ if (transferTokens) {
|
|
|
+ const key = fundsSource || (await this.promptForAccount('Choose source account'))
|
|
|
+ await this.sendAndFollowNamedTx(await this.getDecodedPair(key), 'balances', 'transferKeepAlive', [
|
|
|
+ address,
|
|
|
+ missingStakingAccountBalance,
|
|
|
+ ])
|
|
|
+ } else {
|
|
|
+ throw new CLIError('Missing amount not transferred to the staking account, aborting...')
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!stakingStatus) {
|
|
|
+ await this.sendAndFollowNamedTx(await this.getDecodedPair(address), 'members', 'addStakingAccountCandidate', [
|
|
|
+ memberId,
|
|
|
+ ])
|
|
|
+ }
|
|
|
|
|
|
- break
|
|
|
+ if (!stakingStatus || stakingStatus.confirmed.isFalse) {
|
|
|
+ await this.sendAndFollowNamedTx(
|
|
|
+ await this.getDecodedPair(member.controller_account.toString()),
|
|
|
+ 'members',
|
|
|
+ 'confirmStakingAccount',
|
|
|
+ [memberId, address]
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
- return stakingAccount
|
|
|
+ return address
|
|
|
+ }
|
|
|
+
|
|
|
+ async promptForStakingAccount(requiredStake: BN, memberId: MemberId, member: Membership): Promise<string> {
|
|
|
+ this.log(`Required stake: ${formatBalance(requiredStake)}`)
|
|
|
+ while (true) {
|
|
|
+ const stakingAccount = await this.promptForAnyAddress('Choose staking account')
|
|
|
+ try {
|
|
|
+ await this.setupStakingAccount(memberId, member, stakingAccount.toString(), requiredStake)
|
|
|
+ return stakingAccount
|
|
|
+ } catch (e) {
|
|
|
+ if (e instanceof CLIError) {
|
|
|
+ this.warn(e.message)
|
|
|
+ } else {
|
|
|
+ throw e
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
async init(): Promise<void> {
|