diff --git a/scripts/cc-cli/src/commands/withdrawUnbonded.ts b/scripts/cc-cli/src/commands/withdrawUnbonded.ts index e7969e23fd..a1b72a624c 100644 --- a/scripts/cc-cli/src/commands/withdrawUnbonded.ts +++ b/scripts/cc-cli/src/commands/withdrawUnbonded.ts @@ -1,5 +1,6 @@ import { Command, OptionValues } from "commander"; import { newApi } from "../api"; +import { getStatus, requireStatus } from "../utils/status"; import { getControllerSeedFromEnvOrPrompt, initKeyringPair, @@ -8,7 +9,6 @@ import { export function makeWithdrawUnbondedCommand() { const cmd = new Command("withdraw-unbonded"); cmd.description("Withdraw unbonded funds from a stash account"); - cmd.option("-a, --amount [amount]", "Amount to withdraw"); cmd.action(withdrawUnbondedAction); return cmd; } @@ -18,6 +18,23 @@ async function withdrawUnbondedAction(options: OptionValues) { const controllerSeed = await getControllerSeedFromEnvOrPrompt(); const controller = initKeyringPair(controllerSeed); + + const controllerStatus = await getStatus(controller.address, api); + + if (!controllerStatus.stash) { + console.error( + `Could not find stash account associated with the provided controller address: ${controller.address}. Please ensure the address is actually a controller.` + ); + process.exit(1); + } + + const status = await getStatus(controllerStatus.stash, api); + requireStatus( + status, + "canWithdraw", + "Cannot perform action, there are no unlocked funds to withdraw" + ); + const slashingSpans = await api.query.staking.slashingSpans( controller.address ); diff --git a/scripts/cc-cli/src/utils/status.ts b/scripts/cc-cli/src/utils/status.ts index 080c88a9fa..c403445a69 100644 --- a/scripts/cc-cli/src/utils/status.ts +++ b/scripts/cc-cli/src/utils/status.ts @@ -32,7 +32,7 @@ export async function getStatus(address: string, api: ApiPromise) { : undefined; const unlockingRes = res.stakingLedger.unlocking; - const currentEra = await api.query.staking.currentEra(); + const currentEra = (await api.query.staking.currentEra()).unwrap(); const unlocking = unlockingRes ? unlockingRes.filter((u: any) => u.era > currentEra) : []; @@ -41,6 +41,18 @@ export async function getStatus(address: string, api: ApiPromise) { ? readAmountFromHex(res.redeemable.toString()) : new BN(0); + const readyForWithdraw = res.stakingLedger.unlocking + .map((u: any) => { + const chunk: UnlockChunk = { + era: u.era.toNumber(), + value: u.value.toBn(), + }; + return chunk; + }) + .filter((u: UnlockChunk) => u.era < currentEra.toNumber()); + + const canWithdraw = readyForWithdraw.length > 0; + const nextUnbondingDate = unlocking.length > 0 ? unlocking[0].era.toNumber() : null; @@ -71,6 +83,8 @@ export async function getStatus(address: string, api: ApiPromise) { validating: validatorEntries.includes(address), waiting: waitingValidators.includes(address), active: activeValidators.includes(address), + canWithdraw, + readyForWithdraw, nextUnbondingDate, nextUnbondingAmount: nextUnbondingAmount ? nextUnbondingAmount : new BN(0), redeemable, @@ -87,6 +101,16 @@ export async function printValidatorStatus(status: Status, api: ApiPromise) { console.log("Waiting: ", status.waiting); console.log("Active: ", status.active); + console.log("Can withdraw: ", status.canWithdraw); + if (status.canWithdraw) { + console.log("Unlocked chunks: "); + status.readyForWithdraw.forEach((chunk) => { + console.log( + ` ${toCTCString(chunk.value)} unlocked since era ${chunk.era}` + ); + }); + } + let nextUnlocking = "None"; if (status.nextUnbondingAmount && status.nextUnbondingAmount.eq(new BN(0))) { nextUnlocking = "None"; @@ -100,10 +124,16 @@ export async function printValidatorStatus(status: Status, api: ApiPromise) { console.log(`Next unbonding chunk: ${nextUnlocking}`); } -export function requireStatus(status: Status, condition: keyof Status) { +export function requireStatus( + status: Status, + condition: keyof Status, + message?: string +) { if (!status[condition]) { console.error( - `Cannot perform action, validator is not ${condition.toString()}` + message + ? message + : `Cannot perform action, validator is not ${condition.toString()}` ); process.exit(1); } @@ -116,11 +146,18 @@ export interface Status { validating: boolean; waiting: boolean; active: boolean; + canWithdraw: boolean; + readyForWithdraw: UnlockChunk[]; nextUnbondingDate: Option; nextUnbondingAmount: Option; redeemable: Balance; } +interface UnlockChunk { + era: EraNumber; + value: Balance; +} + type Balance = BN; type EraNumber = number;