diff --git a/addons/rescue_node/addon.go b/addons/rescue_node/addon.go index 936bf3ca8..46f386ab7 100644 --- a/addons/rescue_node/addon.go +++ b/addons/rescue_node/addon.go @@ -12,15 +12,10 @@ import ( "github.com/rocket-pool/smartnode/addons/rescue_node/pb" "github.com/rocket-pool/smartnode/shared/types/addons" cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) const ( - colorReset string = "\033[0m" - colorRed string = "\033[31m" - colorGreen string = "\033[32m" - colorYellow string = "\033[33m" - colorBlue string = "\033[36m" - soloAuthValidity = 10 * time.Hour * 24 rpAuthValidity = 15 * time.Hour * 24 ) @@ -129,33 +124,33 @@ func (r *RescueNode) PrintStatusText(nodeAddr common.Address) { return } - fmt.Printf("%s=== Rescue Node Add-on Enabled ===%s\n", colorYellow, colorReset) + color.YellowPrintln("=== Rescue Node Add-on Enabled ===") // Check the Username usernameNodeAddr, err := r.getCredentialNodeId() if err != nil { - fmt.Printf("%s%v%s\n", colorRed, err, colorReset) + color.RedPrintln(err.Error()) } else { - fmt.Printf("Using a credential issued to %s%s%s.\n", colorBlue, usernameNodeAddr.String(), colorReset) + fmt.Printf("Using a credential issued to %s.\n", color.LightBlue(usernameNodeAddr.String())) if !bytes.Equal(usernameNodeAddr.Bytes(), nodeAddr.Bytes()) { - fmt.Printf("%s - WARNING: This does not match the Node Account!%s\n", colorYellow, colorReset) + color.YellowPrintln(" - WARNING: This does not match the Node Account!") } } credentialDetails, err := r.getCredentialDetails() if err != nil { - fmt.Printf("%s%v%s\n", colorRed, err, colorReset) + color.RedPrintln(err.Error()) } else { if credentialDetails.solo { - fmt.Printf("%s - WARNING: This credential was issued to a solo staker!%s\n", colorYellow, colorReset) + color.YellowPrintln(" - WARNING: This credential was issued to a solo staker!") } timeLeft := credentialDetails.GetTimeLeft().Truncate(time.Second) if timeLeft < 0 { - fmt.Printf("%sWARNING: This credential expired %s ago!%s\n", colorRed, timeLeft.String(), colorReset) + color.RedPrintf("WARNING: This credential expired %s ago!\n", timeLeft.String()) } else if timeLeft < time.Hour*24 { - fmt.Printf("%sWARNING: This credential expires in %s!%s\n", colorYellow, timeLeft.String(), colorReset) + color.YellowPrintf("WARNING: This credential expires in %s!\n", timeLeft.String()) } else { - fmt.Printf("%sThis credential expires in %s.%s\n", colorGreen, timeLeft.String(), colorReset) + color.GreenPrintf("This credential expires in %s.\n", timeLeft.String()) } } } diff --git a/rocketpool-cli/claims/claim-all.go b/rocketpool-cli/claims/claim-all.go index 649d4fe56..6a45f392f 100644 --- a/rocketpool-cli/claims/claim-all.go +++ b/rocketpool-cli/claims/claim-all.go @@ -15,19 +15,12 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/rocket-pool/smartnode/shared/utils/math" "github.com/urfave/cli" ) -const ( - colorReset string = "\033[0m" - colorRed string = "\033[31m" - colorGreen string = "\033[32m" - colorYellow string = "\033[33m" - colorBlue string = "\033[36m" -) - // pendingClaim represents a single category of rewards that can be claimed. type pendingClaim struct { id int @@ -81,25 +74,27 @@ func claimAll(c *cli.Context, statusOnly bool) error { var periodicIntervalIndices []uint64 periodicRestakeResolved := false - fmt.Printf("%s============================================================%s\n", colorGreen, colorReset) - fmt.Printf("%s Available Rewards Summary %s\n", colorGreen, colorReset) - fmt.Printf("%s============================================================%s\n\n", colorGreen, colorReset) + color.GreenPrintln("============================================================") + color.GreenPrintln(" Available Rewards Summary ") + color.GreenPrintln("============================================================") + fmt.Println() // ================================================================ // 1. Megapool EL Rewards (distribute) // ================================================================ sectionID++ id := sectionID - fmt.Printf("%s--- [%d] Megapool Execution Layer Rewards ---%s\n", colorGreen, id, colorReset) + color.GreenPrintf("--- [%d] Megapool Execution Layer Rewards ---\n", id) canDistribute, err := rp.CanDistributeMegapool() if err != nil { - fmt.Printf(" %sCould not check megapool: %s%s\n\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not check megapool: %s\n", err) + fmt.Println() } else if !canDistribute.CanDistribute { if canDistribute.MegapoolNotDeployed { - fmt.Printf(" No megapool deployed.\n\n") + fmt.Println(" No megapool deployed.") } else if canDistribute.LastDistributionTime == 0 { - fmt.Printf(" No staking validators in the megapool.\n\n") + fmt.Println(" No staking validators in the megapool.") } else { reasons := []string{} if canDistribute.ExitingValidatorCount > 0 { @@ -109,26 +104,27 @@ func claimAll(c *cli.Context, statusOnly bool) error { reasons = append(reasons, fmt.Sprintf("%d validator(s) locked", canDistribute.LockedValidatorCount)) } if len(reasons) > 0 { - fmt.Printf(" Cannot distribute: %s\n\n", strings.Join(reasons, ", ")) + fmt.Printf(" Cannot distribute: %s\n", strings.Join(reasons, ", ")) } else { - fmt.Printf(" Cannot distribute at this time.\n\n") + fmt.Println(" Cannot distribute at this time.") } } + fmt.Println() } else { // Get the pending rewards breakdown pendingRewards, err := rp.CalculatePendingRewards() if err != nil { - fmt.Printf(" %sCould not calculate pending rewards: %s%s\n\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not calculate pending rewards: %s\n", err) + fmt.Println() } else { megapoolTotal := new(big.Int).Add(pendingRewards.RewardSplit.NodeRewards, pendingRewards.RefundValue) if megapoolTotal.Cmp(big.NewInt(0)) > 0 { fmt.Printf(" Node share: %.6f ETH\n", math.RoundDown(eth.WeiToEth(pendingRewards.RewardSplit.NodeRewards), 6)) if pendingRewards.RefundValue.Cmp(big.NewInt(0)) > 0 { fmt.Printf(" Refund value: %.6f ETH\n", math.RoundDown(eth.WeiToEth(pendingRewards.RefundValue), 6)) - fmt.Printf(" Total: %.6f ETH\n\n", math.RoundDown(eth.WeiToEth(megapoolTotal), 6)) - } else { - fmt.Println() + fmt.Printf(" Total: %.6f ETH\n", math.RoundDown(eth.WeiToEth(megapoolTotal), 6)) } + fmt.Println() totalEthWei.Add(totalEthWei, megapoolTotal) @@ -139,22 +135,23 @@ func claimAll(c *cli.Context, statusOnly bool) error { ethValue: megapoolTotal, gasInfo: gasInfo, execute: func() error { - fmt.Printf(" Submitting transaction...\n") + fmt.Println(" Submitting transaction...") response, err := rp.DistributeMegapool() if err != nil { return fmt.Errorf("transaction could not be submitted: %w", err) } - fmt.Printf(" Distributing megapool rewards...\n") + fmt.Println(" Distributing megapool rewards...") cliutils.PrintTransactionHash(rp, response.TxHash) if _, err = rp.WaitForTransaction(response.TxHash); err != nil { return fmt.Errorf("transaction was submitted but failed onchain: %w", err) } - fmt.Printf(" %sSuccessfully distributed megapool rewards.%s\n", colorGreen, colorReset) + color.GreenPrintln("Successfully distributed megapool rewards.") return nil }, }) } else { - fmt.Printf(" No pending rewards to distribute.\n\n") + fmt.Println(" No pending rewards to distribute.") + fmt.Println() } } } @@ -164,26 +161,31 @@ func claimAll(c *cli.Context, statusOnly bool) error { // ================================================================ sectionID++ feeDistID := sectionID - fmt.Printf("%s--- [%d] Fee Distributor ---%s\n", colorGreen, feeDistID, colorReset) + color.GreenPrintf("--- [%d] Fee Distributor ---\n", feeDistID) isInitResponse, err := rp.IsFeeDistributorInitialized() if err != nil { - fmt.Printf(" %sCould not check fee distributor: %s%s\n\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not check fee distributor: %s\n", err) + fmt.Println() } else if !isInitResponse.IsInitialized { - fmt.Printf(" Fee distributor not initialized. Run 'rocketpool node initialize-fee-distributor' first.\n\n") + fmt.Println(" Fee distributor not initialized. Run 'rocketpool node initialize-fee-distributor' first.") + fmt.Println() } else { canDistResp, err := rp.CanDistribute() if err != nil { - fmt.Printf(" %sCould not check fee distributor balance: %s%s\n\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not check fee distributor balance: %s\n", err) + fmt.Println() } else { balance := eth.WeiToEth(canDistResp.Balance) if balance == 0 { - fmt.Printf(" No balance in fee distributor.\n\n") + fmt.Println(" No balance in fee distributor.") + fmt.Println() } else { rEthShare := balance - canDistResp.NodeShare fmt.Printf(" Distributor balance: %.6f ETH\n", math.RoundDown(balance, 6)) fmt.Printf(" Your share: %.6f ETH\n", math.RoundDown(canDistResp.NodeShare, 6)) - fmt.Printf(" rETH stakers share: %.6f ETH\n\n", math.RoundDown(rEthShare, 6)) + fmt.Printf(" rETH stakers share: %.6f ETH\n", math.RoundDown(rEthShare, 6)) + fmt.Println() nodeShareWei := eth.EthToWei(canDistResp.NodeShare) totalEthWei.Add(totalEthWei, nodeShareWei) @@ -195,17 +197,17 @@ func claimAll(c *cli.Context, statusOnly bool) error { ethValue: nodeShareWei, gasInfo: gasInfo, execute: func() error { - fmt.Printf(" Submitting transaction...\n") + fmt.Println(" Submitting transaction...") response, err := rp.Distribute() if err != nil { return fmt.Errorf("transaction could not be submitted: %w", err) } - fmt.Printf(" Distributing fee distributor balance...\n") + fmt.Println(" Distributing fee distributor balance...") cliutils.PrintTransactionHash(rp, response.TxHash) if _, err = rp.WaitForTransaction(response.TxHash); err != nil { return fmt.Errorf("transaction was submitted but failed on-chain: %w", err) } - fmt.Printf(" %sSuccessfully distributed fee distributor balance.%s\n", colorGreen, colorReset) + color.GreenPrintln("Successfully distributed fee distributor balance.") return nil }, }) @@ -218,11 +220,12 @@ func claimAll(c *cli.Context, statusOnly bool) error { // ================================================================ sectionID++ minipoolID := sectionID - fmt.Printf("%s--- [%d] Minipool Balance Distribution ---%s\n", colorGreen, minipoolID, colorReset) + color.GreenPrintf("--- [%d] Minipool Balance Distribution ---\n", minipoolID) minipoolDetails, err := rp.GetDistributeBalanceDetails() if err != nil { - fmt.Printf(" %sCould not check minipool balances: %s%s\n\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not check minipool balances: %s\n", err) + fmt.Println() } else { eligibleMinipools := []api.MinipoolBalanceDistributionDetails{} for _, mp := range minipoolDetails.Details { @@ -232,7 +235,8 @@ func claimAll(c *cli.Context, statusOnly bool) error { } if len(eligibleMinipools) == 0 { - fmt.Printf(" No minipools eligible for balance distribution.\n\n") + fmt.Println(" No minipools eligible for balance distribution.") + fmt.Println() } else { // Sort by balance (highest first) sort.Slice(eligibleMinipools, func(i, j int) bool { @@ -266,7 +270,8 @@ func claimAll(c *cli.Context, statusOnly bool) error { mpTotalEth.Add(mpTotalEth, nodeAmount) } } - fmt.Printf(" Total from %d minipool(s): %.6f ETH\n\n", len(eligibleMinipools), math.RoundDown(eth.WeiToEth(mpTotalEth), 6)) + fmt.Printf(" Total from %d minipool(s): %.6f ETH\n", len(eligibleMinipools), math.RoundDown(eth.WeiToEth(mpTotalEth), 6)) + fmt.Println() totalEthWei.Add(totalEthWei, mpTotalEth) // Accumulate gas @@ -293,17 +298,17 @@ func claimAll(c *cli.Context, statusOnly bool) error { fmt.Printf(" Submitting transaction for minipool %s...\n", mp.Address.Hex()) response, err := rp.DistributeBalance(mp.Address) if err != nil { - fmt.Printf(" %sFailed to distribute minipool %s: %s%s\n", colorRed, mp.Address.Hex(), err, colorReset) + color.RedPrintf(" Failed to distribute minipool %s: %s\n", mp.Address.Hex(), err) failCount++ continue } fmt.Printf(" Distributing balance of minipool %s...\n", mp.Address.Hex()) cliutils.PrintTransactionHash(rp, response.TxHash) if _, err = rp.WaitForTransaction(response.TxHash); err != nil { - fmt.Printf(" %sTransaction failed for minipool %s: %s%s\n", colorRed, mp.Address.Hex(), err, colorReset) + color.RedPrintf(" Transaction failed for minipool %s: %s\n", mp.Address.Hex(), err) failCount++ } else { - fmt.Printf(" %sSuccessfully distributed balance of minipool %s.%s\n", colorGreen, mp.Address.Hex(), colorReset) + color.GreenPrintf("Successfully distributed balance of minipool %s.\n", mp.Address.Hex()) } } if failCount > 0 { @@ -320,13 +325,15 @@ func claimAll(c *cli.Context, statusOnly bool) error { // ================================================================ sectionID++ periodicID := sectionID - fmt.Printf("%s--- [%d] Periodic Rewards (RPL + ETH) ---%s\n", colorGreen, periodicID, colorReset) + color.GreenPrintf("--- [%d] Periodic Rewards (RPL + ETH) ---\n", periodicID) rewardsInfo, err := rp.GetRewardsInfo() if err != nil { - fmt.Printf(" %sCould not check periodic rewards: %s%s\n\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not check periodic rewards: %s\n", err) + fmt.Println() } else if !rewardsInfo.Registered { - fmt.Printf(" Node is not registered.\n\n") + fmt.Println(" Node is not registered.") + fmt.Println() } else { // Handle missing/invalid merkle trees missingIntervals := []int{} @@ -336,11 +343,11 @@ func claimAll(c *cli.Context, statusOnly bool) error { } } if len(missingIntervals) > 0 && !statusOnly { - fmt.Printf(" %sMissing or invalid Merkle tree files for intervals: %v%s\n", colorYellow, missingIntervals, colorReset) + color.YellowPrintf(" Missing or invalid Merkle tree files for intervals: %v\n", missingIntervals) if autoConfirm || prompt.Confirm(" Would you like to download the missing rewards tree files?") { cfg, _, err := rp.LoadConfig() if err != nil { - fmt.Printf(" %sCould not load config for tree download: %s%s\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not load config for tree download: %s\n", err) } else { for _, interval := range rewardsInfo.InvalidIntervals { if !interval.TreeFileExists || !interval.MerkleRootValid { @@ -356,7 +363,8 @@ func claimAll(c *cli.Context, statusOnly bool) error { // Reload rewards info rewardsInfo, err = rp.GetRewardsInfo() if err != nil { - fmt.Printf(" %sCould not reload rewards info: %s%s\n\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not reload rewards info: %s\n", err) + fmt.Println() } } } @@ -417,7 +425,7 @@ func claimAll(c *cli.Context, statusOnly bool) error { var gasInfo rocketpoolapi.GasInfo canClaim, canErr := rp.CanNodeClaimRewards(intervalIndices) if canErr != nil { - fmt.Printf(" %sWarning: could not estimate gas for periodic rewards: %s%s\n", colorYellow, canErr, colorReset) + color.YellowPrintf(" Warning: could not estimate gas for periodic rewards: %s\n", canErr) } else { gasInfo = canClaim.GasInfo } @@ -429,7 +437,7 @@ func claimAll(c *cli.Context, statusOnly bool) error { rplValue: prTotalRpl, gasInfo: gasInfo, execute: func() error { - fmt.Printf(" Submitting transaction...\n") + fmt.Println(" Submitting transaction...") var txHash common.Hash if periodicRestakeAmount == nil { response, err := rp.NodeClaimRewards(periodicIntervalIndices) @@ -444,15 +452,15 @@ func claimAll(c *cli.Context, statusOnly bool) error { } txHash = response.TxHash } - fmt.Printf(" Claiming periodic rewards...\n") + fmt.Println(" Claiming periodic rewards...") cliutils.PrintTransactionHash(rp, txHash) if _, err := rp.WaitForTransaction(txHash); err != nil { return fmt.Errorf("transaction was submitted but failed on-chain: %w", err) } if periodicRestakeAmount != nil { - fmt.Printf(" %sSuccessfully claimed rewards and restaked %.6f RPL.%s\n", colorGreen, eth.WeiToEth(periodicRestakeAmount), colorReset) + color.GreenPrintf("Successfully claimed rewards and restaked %.6f RPL.\n", eth.WeiToEth(periodicRestakeAmount)) } else { - fmt.Printf(" %sSuccessfully claimed periodic rewards.%s\n", colorGreen, colorReset) + color.GreenPrintln("Successfully claimed periodic rewards.") } return nil }, @@ -468,25 +476,30 @@ func claimAll(c *cli.Context, statusOnly bool) error { nodeStatus, err := rp.NodeStatus() if err != nil { sectionID++ - fmt.Printf("%s--- [%d] Unclaimed Rewards ---%s\n", colorGreen, sectionID, colorReset) - fmt.Printf(" %sCould not check node status: %s%s\n\n", colorYellow, err, colorReset) + color.GreenPrintf("--- [%d] Unclaimed Rewards ---\n", sectionID) + color.YellowPrintf(" Could not check node status: %s\n", err) + fmt.Println() sectionID++ - fmt.Printf("%s--- [%d] Credit Balance Withdrawal ---%s\n", colorGreen, sectionID, colorReset) - fmt.Printf(" %sCould not check node status: %s%s\n\n", colorYellow, err, colorReset) + color.GreenPrintf("--- [%d] Credit Balance Withdrawal ---\n", sectionID) + color.YellowPrintf(" Could not check node status: %s\n", err) + fmt.Println() sectionID++ - fmt.Printf("%s--- [%d] Staked ETH on Behalf Withdrawal ---%s\n", colorGreen, sectionID, colorReset) - fmt.Printf(" %sCould not check node status: %s%s\n\n", colorYellow, err, colorReset) + color.GreenPrintf("--- [%d] Staked ETH on Behalf Withdrawal ---\n", sectionID) + color.YellowPrintf(" Could not check node status: %s\n", err) + fmt.Println() } else { // --- Unclaimed Rewards --- sectionID++ unclaimedID := sectionID - fmt.Printf("%s--- [%d] Unclaimed Rewards ---%s\n", colorGreen, unclaimedID, colorReset) + color.GreenPrintf("--- [%d] Unclaimed Rewards ---\n", unclaimedID) if nodeStatus.UnclaimedRewards == nil || nodeStatus.UnclaimedRewards.Cmp(big.NewInt(0)) <= 0 { - fmt.Printf(" No unclaimed rewards.\n\n") + fmt.Println(" No unclaimed rewards.") + fmt.Println() } else { fmt.Printf(" Unclaimed rewards: %.6f ETH\n", math.RoundDown(eth.WeiToEth(nodeStatus.UnclaimedRewards), 6)) - fmt.Printf(" (Rewards distributed previously but not yet sent to withdrawal address)\n\n") + fmt.Println(" (Rewards distributed previously but not yet sent to withdrawal address)") + fmt.Println() totalEthWei.Add(totalEthWei, nodeStatus.UnclaimedRewards) nodeAddr := nodeStatus.AccountAddress @@ -494,9 +507,9 @@ func claimAll(c *cli.Context, statusOnly bool) error { var gasInfo rocketpoolapi.GasInfo canClaimOk := false if canErr != nil { - fmt.Printf(" %sWarning: could not estimate gas: %s%s\n", colorYellow, canErr, colorReset) + color.YellowPrintf(" Warning: could not estimate gas: %s\n", canErr) } else if !canClaim.CanClaim { - fmt.Printf(" %sCannot claim unclaimed rewards at this time.%s\n", colorYellow, colorReset) + color.YellowPrintln(" Cannot claim unclaimed rewards at this time.") } else { gasInfo = canClaim.GasInfo canClaimOk = true @@ -509,17 +522,17 @@ func claimAll(c *cli.Context, statusOnly bool) error { ethValue: nodeStatus.UnclaimedRewards, gasInfo: gasInfo, execute: func() error { - fmt.Printf(" Submitting transaction...\n") + fmt.Println(" Submitting transaction...") response, err := rp.ClaimUnclaimedRewards(nodeAddr) if err != nil { return fmt.Errorf("transaction could not be submitted: %w", err) } - fmt.Printf(" Claiming unclaimed rewards...\n") + fmt.Println(" Claiming unclaimed rewards...") cliutils.PrintTransactionHash(rp, response.TxHash) if _, err = rp.WaitForTransaction(response.TxHash); err != nil { return fmt.Errorf("transaction was submitted but failed on-chain: %w", err) } - fmt.Printf(" %sSuccessfully claimed unclaimed rewards.%s\n", colorGreen, colorReset) + color.GreenPrintln("Successfully claimed unclaimed rewards.") return nil }, }) @@ -529,13 +542,14 @@ func claimAll(c *cli.Context, statusOnly bool) error { // --- Credit Balance Withdrawal --- sectionID++ creditID := sectionID - fmt.Printf("%s--- [%d] Credit Balance Withdrawal ---%s\n", colorGreen, creditID, colorReset) + color.GreenPrintf("--- [%d] Credit Balance Withdrawal ---\n", creditID) if nodeStatus.CreditBalance == nil || nodeStatus.CreditBalance.Cmp(big.NewInt(0)) <= 0 { - fmt.Printf(" No credit balance available.\n\n") + fmt.Println(" No credit balance available.") + fmt.Println() } else { creditBalance := nodeStatus.CreditBalance - fmt.Printf(" Credit balance: %.6f ETH (the equivalent amount in rETH will be transferred to %s)\n\n", + fmt.Printf(" Credit balance: %.6f ETH (the equivalent amount in rETH will be transferred to %s)\n", math.RoundDown(eth.WeiToEth(creditBalance), 6), nodeStatus.PrimaryWithdrawalAddress) totalEthWei.Add(totalEthWei, creditBalance) @@ -543,12 +557,12 @@ func claimAll(c *cli.Context, statusOnly bool) error { var gasInfo rocketpoolapi.GasInfo canWithdrawOk := false if canErr != nil { - fmt.Printf(" %sWarning: could not estimate gas: %s%s\n", colorYellow, canErr, colorReset) + color.YellowPrintf(" Warning: could not estimate gas: %s\n", canErr) } else if !canWithdraw.CanWithdraw { if canWithdraw.InsufficientBalance { - fmt.Printf(" %sInsufficient credit balance.%s\n", colorYellow, colorReset) + color.YellowPrintln(" Insufficient credit balance.") } else { - fmt.Printf(" %sCannot withdraw credit at this time.%s\n", colorYellow, colorReset) + color.YellowPrintln(" Cannot withdraw credit at this time.") } } else { gasInfo = canWithdraw.GasInfo @@ -563,17 +577,17 @@ func claimAll(c *cli.Context, statusOnly bool) error { ethValue: withdrawAmount, gasInfo: gasInfo, execute: func() error { - fmt.Printf(" Submitting transaction...\n") + fmt.Println(" Submitting transaction...") response, err := rp.NodeWithdrawCredit(withdrawAmount) if err != nil { return fmt.Errorf("transaction could not be submitted: %w", err) } - fmt.Printf(" Withdrawing credit balance...\n") + fmt.Println(" Withdrawing credit balance...") cliutils.PrintTransactionHash(rp, response.TxHash) if _, err = rp.WaitForTransaction(response.TxHash); err != nil { return fmt.Errorf("transaction was submitted but failed on-chain: %w", err) } - fmt.Printf(" %sSuccessfully withdrew %.6f credit as rETH.%s\n", colorGreen, math.RoundDown(eth.WeiToEth(withdrawAmount), 6), colorReset) + color.GreenPrintf("Successfully withdrew %.6f credit as rETH.\n", math.RoundDown(eth.WeiToEth(withdrawAmount), 6)) return nil }, }) @@ -583,27 +597,29 @@ func claimAll(c *cli.Context, statusOnly bool) error { // --- Staked ETH on Behalf Withdrawal --- sectionID++ ethOnBehalfID := sectionID - fmt.Printf("%s--- [%d] Staked ETH on Behalf Withdrawal ---%s\n", colorGreen, ethOnBehalfID, colorReset) + color.GreenPrintf("--- [%d] Staked ETH on Behalf Withdrawal ---\n", ethOnBehalfID) if nodeStatus.EthOnBehalfBalance == nil || nodeStatus.EthOnBehalfBalance.Cmp(big.NewInt(0)) <= 0 { - fmt.Printf(" No ETH staked on behalf of the node.\n\n") + fmt.Println(" No ETH staked on behalf of the node.") + fmt.Println() } else { ethOnBehalf := nodeStatus.EthOnBehalfBalance - fmt.Printf(" Staked ETH on behalf: %.6f ETH\n\n", math.RoundDown(eth.WeiToEth(ethOnBehalf), 6)) + fmt.Printf(" Staked ETH on behalf: %.6f ETH\n", math.RoundDown(eth.WeiToEth(ethOnBehalf), 6)) + fmt.Println() totalEthWei.Add(totalEthWei, ethOnBehalf) canWithdraw, canErr := rp.CanNodeWithdrawEth(ethOnBehalf) var gasInfo rocketpoolapi.GasInfo canWithdrawOk := false if canErr != nil { - fmt.Printf(" %sWarning: could not estimate gas: %s%s\n", colorYellow, canErr, colorReset) + color.YellowPrintf(" Warning: could not estimate gas: %s\n", canErr) } else if !canWithdraw.CanWithdraw { if canWithdraw.InsufficientBalance { - fmt.Printf(" %sInsufficient staked ETH balance.%s\n", colorYellow, colorReset) + color.YellowPrintln(" Insufficient staked ETH balance.") } else if canWithdraw.HasDifferentWithdrawalAddress { - fmt.Printf(" %sCannot withdraw: primary withdrawal address is set and differs from the node address.%s\n", colorYellow, colorReset) + color.YellowPrintln(" Cannot withdraw: primary withdrawal address is set and differs from the node address.") } else { - fmt.Printf(" %sCannot withdraw staked ETH at this time.%s\n", colorYellow, colorReset) + color.YellowPrintln(" Cannot withdraw staked ETH at this time.") } } else { gasInfo = canWithdraw.GasInfo @@ -618,17 +634,17 @@ func claimAll(c *cli.Context, statusOnly bool) error { ethValue: withdrawAmount, gasInfo: gasInfo, execute: func() error { - fmt.Printf(" Submitting transaction...\n") + fmt.Println(" Submitting transaction...") response, err := rp.NodeWithdrawEth(withdrawAmount) if err != nil { return fmt.Errorf("transaction could not be submitted: %w", err) } - fmt.Printf(" Withdrawing staked ETH...\n") + fmt.Println(" Withdrawing staked ETH...") cliutils.PrintTransactionHash(rp, response.TxHash) if _, err = rp.WaitForTransaction(response.TxHash); err != nil { return fmt.Errorf("transaction was submitted but failed on-chain: %w", err) } - fmt.Printf(" %sSuccessfully withdrew %.6f staked ETH.%s\n", colorGreen, math.RoundDown(eth.WeiToEth(withdrawAmount), 6), colorReset) + color.GreenPrintf("Successfully withdrew %.6f staked ETH.\n", math.RoundDown(eth.WeiToEth(withdrawAmount), 6)) return nil }, }) @@ -641,13 +657,15 @@ func claimAll(c *cli.Context, statusOnly bool) error { // ================================================================ sectionID++ pdaoID := sectionID - fmt.Printf("%s--- [%d] PDAO Bond Claims ---%s\n", colorGreen, pdaoID, colorReset) + color.GreenPrintf("--- [%d] PDAO Bond Claims ---\n", pdaoID) bondsResponse, err := rp.PDAOGetClaimableBonds() if err != nil { - fmt.Printf(" %sCould not check PDAO bonds: %s%s\n\n", colorYellow, err, colorReset) + color.YellowPrintf(" Could not check PDAO bonds: %s\n", err) + fmt.Println() } else if len(bondsResponse.ClaimableBonds) == 0 { - fmt.Printf(" No claimable bonds or rewards.\n\n") + fmt.Println(" No claimable bonds or rewards.") + fmt.Println() } else { pdaoRplTotal := new(big.Int) for _, bond := range bondsResponse.ClaimableBonds { @@ -670,7 +688,7 @@ func claimAll(c *cli.Context, statusOnly bool) error { indices := getClaimIndicesForBond(bond) canResponse, canErr := rp.PDAOCanClaimBonds(bond.ProposalID, indices) if canErr != nil { - fmt.Printf(" %sWarning: could not estimate gas for proposal %d: %s%s\n", colorYellow, bond.ProposalID, canErr, colorReset) + color.YellowPrintf(" Warning: could not estimate gas for proposal %d: %s\n", bond.ProposalID, canErr) allCanClaim = false break } @@ -695,17 +713,17 @@ func claimAll(c *cli.Context, statusOnly bool) error { fmt.Printf(" Submitting transaction for proposal %d...\n", bond.ProposalID) response, err := rp.PDAOClaimBonds(bond.IsProposer, bond.ProposalID, indices) if err != nil { - fmt.Printf(" %sFailed to claim bonds from proposal %d: %s%s\n", colorRed, bond.ProposalID, err, colorReset) + color.RedPrintf(" Failed to claim bonds from proposal %d: %s\n", bond.ProposalID, err) failCount++ continue } fmt.Printf(" Claiming bonds from proposal %d...\n", bond.ProposalID) cliutils.PrintTransactionHash(rp, response.TxHash) if _, err = rp.WaitForTransaction(response.TxHash); err != nil { - fmt.Printf(" %sTransaction failed for proposal %d: %s%s\n", colorRed, bond.ProposalID, err, colorReset) + color.RedPrintf(" Transaction failed for proposal %d: %s\n", bond.ProposalID, err) failCount++ } else { - fmt.Printf(" %sSuccessfully claimed bonds from proposal %d.%s\n", colorGreen, bond.ProposalID, colorReset) + color.GreenPrintf("Successfully claimed bonds from proposal %d.\n", bond.ProposalID) } } if failCount > 0 { @@ -720,9 +738,9 @@ func claimAll(c *cli.Context, statusOnly bool) error { // ================================================================ // Summary // ================================================================ - fmt.Printf("%s============================================================%s\n", colorGreen, colorReset) - fmt.Printf("%s Totals %s\n", colorGreen, colorReset) - fmt.Printf("%s============================================================%s\n", colorGreen, colorReset) + color.GreenPrintf("============================================================\n") + color.GreenPrintf(" Totals \n") + color.GreenPrintf("============================================================\n") fmt.Printf(" ETH: %.6f\n", math.RoundDown(eth.WeiToEth(totalEthWei), 6)) fmt.Printf(" RPL: %.6f\n\n", math.RoundDown(eth.WeiToEth(totalRplWei), 6)) @@ -786,7 +804,8 @@ func claimAll(c *cli.Context, statusOnly bool) error { return nil } - fmt.Printf("\n%d claim(s) selected:\n", len(selectedClaims)) + fmt.Println() + fmt.Printf("%d claim(s) selected:\n", len(selectedClaims)) for i, claim := range selectedClaims { if v := claim.valueString(); v != "" { fmt.Printf(" %d. %s: %s\n", i+1, claim.name, v) @@ -839,6 +858,7 @@ func claimAll(c *cli.Context, statusOnly bool) error { } } } + fmt.Println() // Accumulate total gas for fee estimation var totalGasEst, totalGasSafe uint64 @@ -864,23 +884,25 @@ func claimAll(c *cli.Context, statusOnly bool) error { } // Execute selected claims - fmt.Printf("\n%sExecuting %d claim(s)...%s\n", colorBlue, len(selectedClaims), colorReset) + color.LightBluePrintf("Executing %d claim(s)...\n", len(selectedClaims)) successCount := 0 failCount := 0 skippedCount := 0 for i, claim := range selectedClaims { - fmt.Printf("\n%s[%d/%d] %s%s\n", colorBlue, i+1, len(selectedClaims), claim.name, colorReset) + fmt.Println() + color.LightBluePrintf("[%d/%d] %s\n", i+1, len(selectedClaims), claim.name) g.Assign(rp) err := claim.execute() if err != nil { failCount++ - fmt.Printf("\n %sERROR: %s%s\n", colorRed, err, colorReset) + fmt.Println() + color.RedPrintf(" ERROR: %s\n", err) // If there are more claims and we're not auto-confirming, ask whether to continue remaining := len(selectedClaims) - i - 1 if remaining > 0 { if autoConfirm { - fmt.Printf(" %sContinuing with remaining %d claim(s)...%s\n", colorYellow, remaining, colorReset) + color.YellowPrintf(" Continuing with remaining %d claim(s)...\n", remaining) } else { if !prompt.Confirm(" The above claim failed. Continue with the remaining %d claim(s)?", remaining) { skippedCount = remaining @@ -901,24 +923,24 @@ func claimAll(c *cli.Context, statusOnly bool) error { // Final summary fmt.Println() - fmt.Printf("============================================================\n") + fmt.Println("============================================================") if failCount == 0 && skippedCount == 0 { - fmt.Printf("%sAll %d claim(s) completed successfully.%s\n", colorGreen, successCount, colorReset) + color.GreenPrintf("All %d claim(s) completed successfully.\n", successCount) } else if successCount == 0 { - fmt.Printf("%sAll %d claim(s) failed.%s\n", colorRed, failCount, colorReset) + color.RedPrintf("All %d claim(s) failed.\n", failCount) if skippedCount > 0 { - fmt.Printf("%s%d claim(s) were skipped.%s\n", colorYellow, skippedCount, colorReset) + color.YellowPrintf("%d claim(s) were skipped.\n", skippedCount) } } else { - fmt.Printf("%s%d claim(s) succeeded%s, %s%d failed%s", - colorGreen, successCount, colorReset, - colorRed, failCount, colorReset) + color.GreenPrintf("%d claim(s) succeeded", successCount) + fmt.Printf(", ") + color.RedPrintf("%d claim(s) failed", failCount) if skippedCount > 0 { - fmt.Printf(", %s%d skipped%s", colorYellow, skippedCount, colorReset) + color.YellowPrintf("%d claim(s) were skipped", skippedCount) } fmt.Println(".") } - fmt.Printf("============================================================\n") + fmt.Println("============================================================") if failCount > 0 { return fmt.Errorf("%d of %d claim(s) failed", failCount, failCount+successCount) diff --git a/rocketpool-cli/megapool/deposit.go b/rocketpool-cli/megapool/deposit.go index c5ecd175b..36848b81d 100644 --- a/rocketpool-cli/megapool/deposit.go +++ b/rocketpool-cli/megapool/deposit.go @@ -13,17 +13,14 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/rocket-pool/smartnode/shared/utils/math" ) // Config const ( - colorReset string = "\033[0m" - colorRed string = "\033[31m" - colorGreen string = "\033[32m" - colorYellow string = "\033[33m" - maxCount uint64 = 35 + maxCount uint64 = 35 ) func nodeMegapoolDeposit(c *cli.Context) error { @@ -138,7 +135,10 @@ func nodeMegapoolDeposit(c *cli.Context) error { fmt.Printf("The total bond requirement is %.2f ETH.\n", totalBondRequirementEth) fmt.Println() - if !(c.Bool("yes") || prompt.Confirm("%sNOTE: You are about to create %d new megapool validator(s), requiring a total of: %.2f ETH).%s\nWould you like to continue?", colorYellow, count, totalBondRequirementEth, colorReset)) { + if !(c.Bool("yes") || prompt.Confirm("%s%s", + color.YellowSprintf("NOTE: You are about to create %d new megapool validator(s), requiring a total of: %.2f ETH).\n", count, totalBondRequirementEth), + "Would you like to continue?", + )) { fmt.Println("Cancelled.") return nil } @@ -230,9 +230,10 @@ func nodeMegapoolDeposit(c *cli.Context) error { fmt.Printf("This deposit will use %.6f ETH from your credit balance plus ETH staked on your behalf and will not require any ETH from your node wallet.\n\n", eth.WeiToEth(usableCredit)) } } else { - fmt.Printf("%sNOTE: Your credit balance cannot currently be used to create a new megapool validator.\n"+ - "This may be because the deposit pool has insufficient ETH or the credit balance is not enough to cover any part of the deposit.%s\n"+ - "If you want to continue the deposit now, you will have to pay the full bond amount from your wallet.\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: Your credit balance cannot currently be used to create a new megapool validator.") + color.YellowPrintln("This may be because the deposit pool has insufficient ETH or the credit balance is not enough to cover any part of the deposit.") + color.YellowPrintln("If you want to continue the deposit now, you will have to pay the full bond amount from your wallet.") + fmt.Println() } // Prompt for confirmation if !(c.Bool("yes") || prompt.Confirm("Would you like to continue?")) { @@ -243,24 +244,23 @@ func nodeMegapoolDeposit(c *cli.Context) error { } // Check to see if eth2 is synced - colorReset := "\033[0m" - colorRed := "\033[31m" - colorYellow := "\033[33m" syncResponse, err := rp.NodeSync() if err != nil { - fmt.Printf("%s**WARNING**: Can't verify the sync status of your consensus client.\nYOU WILL LOSE ETH if your megapool validator is activated before it is fully synced.\n"+ - "Reason: %s\n%s", colorRed, err, colorReset) + color.RedPrintln("**WARNING**: Can't verify the sync status of your consensus client.") + color.RedPrintln("YOU WILL LOSE ETH if your megapool validator is activated before it is fully synced.") + color.RedPrintf("Reason: %s\n", err) } else { if syncResponse.BcStatus.PrimaryClientStatus.IsSynced { - fmt.Printf("Your consensus client is synced, you may safely create a megapool validator.\n") + fmt.Println("Your consensus client is synced, you may safely create a megapool validator.") } else if syncResponse.BcStatus.FallbackEnabled { if syncResponse.BcStatus.FallbackClientStatus.IsSynced { - fmt.Printf("Your fallback consensus client is synced, you may safely create a megapool validator.\n") + fmt.Println("Your fallback consensus client is synced, you may safely create a megapool validator.") } else { - fmt.Printf("%s**WARNING**: neither your primary nor fallback consensus clients are fully synced.\nYOU WILL LOSE ETH if your megapool validator is activated before they are fully synced.\n%s", colorRed, colorReset) + color.RedPrintln("**WARNING**: neither your primary nor fallback consensus clients are fully synced.") + color.RedPrintln("YOU WILL LOSE ETH if your megapool validator is activated before they are fully synced.") } } else { - fmt.Printf("%s**WARNING**: your primary consensus client is either not fully synced or offline and you do not have a fallback client configured.\nYOU WILL LOSE ETH if your megapool validator is activated before it is fully synced.\n%s", colorRed, colorReset) + color.RedPrintln("**WARNING**: your primary consensus client is either not fully synced or offline and you do not have a fallback client configured.") } } @@ -272,13 +272,11 @@ func nodeMegapoolDeposit(c *cli.Context) error { // Prompt for confirmation - if !(c.Bool("yes") || prompt.Confirm( - "You are about to deposit %.6f ETH to create %d new megapool validator(s).\n"+ - "%sARE YOU SURE YOU WANT TO DO THIS? %s\n", + if !(c.Bool("yes") || prompt.Confirm("You are about to deposit %.6f ETH to create %d new megapool validator(s).\n%s", math.RoundDown(eth.WeiToEth(totalBondRequirement), 6), count, - colorYellow, - colorReset)) { + color.Yellow("ARE YOU SURE YOU WANT TO DO THIS?"), + )) { fmt.Println("Cancelled.") return nil } diff --git a/rocketpool-cli/megapool/exit-validator.go b/rocketpool-cli/megapool/exit-validator.go index 2c9643746..a8c48e0b5 100644 --- a/rocketpool-cli/megapool/exit-validator.go +++ b/rocketpool-cli/megapool/exit-validator.go @@ -6,6 +6,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) @@ -76,11 +77,13 @@ func exitValidator(c *cli.Context) error { } // Show a warning message - fmt.Printf("%sNOTE:\n", colorYellow) - fmt.Println("You are about to exit a validator. This will tell each the validator to stop all activities on the Beacon Chain.") - fmt.Println("Please continue to run your validators until each one you've exited has been processed by the exit queue.\nYou can watch their progress on the https://beaconcha.in explorer.") - fmt.Println("Your funds will be locked on the Beacon Chain until they've been withdrawn, which will happen automatically (this may take a few days).") - fmt.Printf("Once your funds have been withdrawn, you can run `rocketpool megapool notify-validator-exit` to distribute them to your withdrawal address.\n\n%s", colorReset) + color.YellowPrintln("NOTE:") + color.YellowPrintln("You are about to exit a validator. This will tell each the validator to stop all activities on the Beacon Chain.") + color.YellowPrintln("Please continue to run your validators until each one you've exited has been processed by the exit queue.") + color.YellowPrintln("You can watch their progress on the https://beaconcha.in explorer.") + color.YellowPrintln("Your funds will be locked on the Beacon Chain until they've been withdrawn, which will happen automatically (this may take a few days).") + color.YellowPrintln("Once your funds have been withdrawn, you can run `rocketpool megapool notify-validator-exit` to distribute them to your withdrawal address.") + fmt.Println() // Prompt for confirmation if !(c.Bool("yes") || prompt.Confirm("Are you sure you want to EXIT validator id %d?", validatorId)) { diff --git a/rocketpool-cli/megapool/notify-final-balance.go b/rocketpool-cli/megapool/notify-final-balance.go index e9356b8fc..dd475f6bd 100644 --- a/rocketpool-cli/megapool/notify-final-balance.go +++ b/rocketpool-cli/megapool/notify-final-balance.go @@ -12,6 +12,7 @@ import ( "github.com/rocket-pool/smartnode/shared/types/api" cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) @@ -89,7 +90,7 @@ func notifyFinalBalance(c *cli.Context) error { } } - fmt.Printf("%sFetching the beacon state to craft a final balance proof. This process can take several minutes and is CPU and memory intensive.%s", colorYellow, colorReset) + color.YellowPrintln("Fetching the beacon state to craft a final balance proof. This process can take several minutes and is CPU and memory intensive.") fmt.Println() response, err := rp.CanNotifyFinalBalance(validatorId, slot) diff --git a/rocketpool-cli/megapool/status.go b/rocketpool-cli/megapool/status.go index cdb20d463..8fd1af54d 100644 --- a/rocketpool-cli/megapool/status.go +++ b/rocketpool-cli/megapool/status.go @@ -10,13 +10,13 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/math" "github.com/urfave/cli" ) const ( - TimeFormat = "2006-01-02, 15:04 -0700 MST" - colorBlue string = "\033[36m" + TimeFormat = "2006-01-02, 15:04 -0700 MST" ) func getStatus(c *cli.Context) error { @@ -59,10 +59,10 @@ func getStatus(c *cli.Context) error { } // Address, express tickets, validator count - fmt.Printf("%s=== Megapool ===%s\n", colorGreen, colorReset) - fmt.Printf("The node has a megapool deployed at %s%s%s\n", colorBlue, status.Megapool.Address, colorReset) + color.GreenPrintln("=== Megapool ===") + fmt.Printf("The node has a megapool deployed at %s\n", color.LightBlue(status.Megapool.Address.String())) if status.Megapool.DelegateExpired { - fmt.Printf("%sThe megapool delegate is expired.%s\n", colorRed, colorReset) + color.RedPrintln("The megapool delegate is expired.") fmt.Println("Upgrade your megapool delegate using 'rocketpool megapool delegate-upgrade' to view the express ticket and validator count.") } else { fmt.Printf("The node has %d express ticket(s).\n", status.Megapool.NodeExpressTicketCount) @@ -71,19 +71,19 @@ func getStatus(c *cli.Context) error { fmt.Println() // Delegate addresses - fmt.Printf("%s=== Megapool Delegate ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Megapool Delegate ===") if status.Megapool.DelegateExpired { - fmt.Printf("%sThe megapool delegate is expired.%s\n", colorRed, colorReset) - fmt.Printf("The megapool is using an expired delegate at %s%s%s\n", colorBlue, status.Megapool.DelegateAddress, colorReset) - fmt.Printf("The megapool can be upgraded to delegate %s%s%s using 'rocketpool megapool delegate-upgrade'.\n", colorBlue, status.LatestDelegate, colorReset) + color.RedPrintln("The megapool delegate is expired.") + fmt.Printf("The megapool is using an expired delegate at %s\n", color.LightBlue(status.Megapool.DelegateAddress.String())) + fmt.Printf("The megapool can be upgraded to delegate %s using 'rocketpool megapool delegate-upgrade'.\n", color.LightBlue(status.LatestDelegate.String())) } else { if status.Megapool.EffectiveDelegateAddress == status.LatestDelegate { fmt.Println("The megapool is using the latest delegate.") } else { - fmt.Printf("The megapool is using an outdated delegate at %s%s%s\n", colorBlue, status.Megapool.DelegateAddress, colorReset) - fmt.Printf("The megapool can be upgraded to delegate %s%s%s using 'rocketpool megapool delegate-upgrade'.\n", colorBlue, status.LatestDelegate, colorReset) + fmt.Printf("The megapool is using an outdated delegate at %s\n", color.LightBlue(status.Megapool.DelegateAddress.String())) + fmt.Printf("The megapool can be upgraded to delegate %s using 'rocketpool megapool delegate-upgrade'.\n", color.LightBlue(status.LatestDelegate.String())) } - fmt.Printf("The megapool's effective delegate address is %s%s%s\n", colorBlue, status.Megapool.EffectiveDelegateAddress, colorReset) + fmt.Printf("The megapool's effective delegate address is %s\n", color.LightBlue(status.Megapool.EffectiveDelegateAddress.String())) } if status.Megapool.UseLatestDelegate { @@ -91,13 +91,13 @@ func getStatus(c *cli.Context) error { } else { fmt.Println("The megapool has automatic delegate upgrades disabled. You can toggle this setting using 'rocketpool megapool set-use-latest-delegate'.") if status.Megapool.DelegateExpiry > 0 { - fmt.Printf("Your current megapool delegate expires at %sblock %d%s.\n", colorBlue, status.Megapool.DelegateExpiry, colorReset) + fmt.Printf("Your current megapool delegate expires at %s.\n", color.LightBlueSprintf("block %d", status.Megapool.DelegateExpiry)) } } fmt.Println() // Balance and network commission - fmt.Printf("%s=== Megapool Balance ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Megapool Balance ===") if !status.Megapool.DelegateExpired { totalBond := new(big.Int).Mul(status.Megapool.NodeBond, big.NewInt(8)) rpBond := new(big.Int).Sub(totalBond, status.Megapool.NodeBond) @@ -172,7 +172,7 @@ func getValidatorStatus(c *cli.Context) error { // Return if delegate is expired if status.Megapool.DelegateExpired { - fmt.Printf("%sThe megapool delegate is expired.%s\n", colorRed, colorReset) + color.RedPrintln("The megapool delegate is expired.") fmt.Println("Upgrade your megapool delegate using 'rocketpool megapool delegate-upgrade' to view the validator info.") return nil } diff --git a/rocketpool-cli/minipool/close.go b/rocketpool-cli/minipool/close.go index 549a4c5e8..964e1da31 100644 --- a/rocketpool-cli/minipool/close.go +++ b/rocketpool-cli/minipool/close.go @@ -16,14 +16,11 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/rocket-pool/smartnode/shared/utils/math" ) -const ( - colorBlue string = "\033[36m" -) - func closeMinipools(c *cli.Context) error { // Get RP client @@ -41,11 +38,11 @@ func closeMinipools(c *cli.Context) error { // Post a warning about express ticket provisioning if !details.ExpressTicketsProvisioned { - if !prompt.Confirm("%sWARNING: The node has unprovisioned express queue ticket(s). Closing minipool(s) without provisioning will reduce the number of express queue tickets the node is eligible for. Please enter `yes` if you've understood this message.%s`", colorRed, colorReset) { + if !prompt.ConfirmRed("WARNING: The node has unprovisioned express queue ticket(s). Closing minipool(s) without provisioning will reduce the number of express queue tickets the node is eligible for. Please enter `yes` if you've understood this message.") { fmt.Println("Cancelled.") return nil } - if c.Bool("yes") || prompt.Confirm("%sWould you like to provision express queue tickets for the node?%s", colorYellow, colorReset) { + if c.Bool("yes") || prompt.ConfirmYellow("Would you like to provision express queue tickets for the node?") { // Check if the node can provision express tickets canProvision, err := rp.CanProvisionExpressTickets() if err != nil { @@ -81,7 +78,8 @@ func closeMinipools(c *cli.Context) error { // Exit if the fee distributor hasn't been initialized yet if !details.IsFeeDistributorInitialized { - fmt.Println("Minipools cannot be closed until your fee distributor has been initialized.\nPlease run `rocketpool node initialize-fee-distributor` first, then return here to close your minipools.") + fmt.Println("Minipools cannot be closed until your fee distributor has been initialized.") + fmt.Println("Please run `rocketpool node initialize-fee-distributor` first, then return here to close your minipools.") return nil } @@ -117,25 +115,31 @@ func closeMinipools(c *cli.Context) error { // Print ineligible ones if len(unwithdrawnMinipools) > 0 { - fmt.Printf("%sNOTE: The following minipools have not had their full balances withdrawn from the Beacon Chain yet:\n", colorBlue) + color.LightBluePrintln("NOTE: The following minipools have not had their full balances withdrawn from the Beacon Chain yet:") for _, mp := range unwithdrawnMinipools { - fmt.Printf("\t%s\n", mp.Address) + color.LightBluePrintf("\t%s\n", mp.Address) } - fmt.Printf("\nTo close them, first run `rocketpool minipool exit` on them and wait until their balances have been withdrawn.%s\n\n", colorReset) + fmt.Println() + color.LightBluePrintln("To close them, first run `rocketpool minipool exit` on them and wait until their balances have been withdrawn.") + fmt.Println() } if len(versionTooLowMinipools) > 0 { - fmt.Printf("%sWARNING: The following minipools are using an old delegate and cannot be safely closed:\n", colorYellow) + color.YellowPrintln("WARNING: The following minipools are using an old delegate and cannot be safely closed:") for _, mp := range versionTooLowMinipools { - fmt.Printf("\t%s\n", mp.Address) + color.YellowPrintf("\t%s\n", mp.Address) } - fmt.Printf("\nPlease upgrade the delegate for these minipools using `rocketpool minipool delegate-upgrade` in order to close them.%s\n\n", colorReset) + fmt.Println() + color.YellowPrintln("Please upgrade the delegate for these minipools using `rocketpool minipool delegate-upgrade` in order to close them.") + fmt.Println() } if len(balanceLessThanRefundMinipools) > 0 { - fmt.Printf("%sWARNING: The following minipools have refunds larger than their current balances and cannot be closed at this time:\n", colorYellow) + color.YellowPrintln("WARNING: The following minipools have refunds larger than their current balances and cannot be closed at this time:") for _, mp := range balanceLessThanRefundMinipools { - fmt.Printf("\t%s\n", mp.Address) + color.YellowPrintf("\t%s\n", mp.Address) } - fmt.Printf("\nIf you have recently exited their validators from the Beacon Chain, please wait until their balances have been sent to the minipools before closing them.%s\n\n", colorReset) + fmt.Println() + color.YellowPrintln("If you have recently exited their validators from the Beacon Chain, please wait until their balances have been sent to the minipools before closing them.") + fmt.Println() } // Check for closable minipools @@ -207,12 +211,15 @@ func closeMinipools(c *cli.Context) error { // If there isn't enough eth to pay back rETH holders, warn that RPL and ETH will both be penalized if distributableBalance.Cmp(minipool.UserDepositBalance) < 0 { // Less than the user deposit balance, ETH + RPL will be slashed - fmt.Printf("%sWARNING: Minipool %s has a distributable balance of %.6f ETH which is lower than the amount borrowed from the staking pool (%.6f ETH).\nPlease visit the Rocket Pool Discord's #support channel (https://discord.gg/rocketpool) if you are not expecting this.%s\n", colorRed, minipool.Address.Hex(), math.RoundDown(eth.WeiToEth(distributableBalance), 6), math.RoundDown(eth.WeiToEth(minipool.UserDepositBalance), 6), colorReset) + color.RedPrintf("WARNING: Minipool %s has a distributable balance of %.6f ETH which is lower than the amount borrowed from the staking pool (%.6f ETH).\n", minipool.Address.Hex(), math.RoundDown(eth.WeiToEth(distributableBalance), 6), math.RoundDown(eth.WeiToEth(minipool.UserDepositBalance), 6)) + color.RedPrintln("Please visit the Rocket Pool Discord's #support channel (https://discord.gg/rocketpool) if you are not expecting this.") if !c.Bool("confirm-slashing") { - fmt.Printf("\n%sIf you are *sure* you want to close the minipool anyway, rerun this command with the `--confirm-slashing` flag. Doing so WILL RESULT in both your ETH bond and your RPL collateral being slashed.%s\n", colorRed, colorReset) + fmt.Println() + color.RedPrintln("If you are *sure* you want to close the minipool anyway, rerun this command with the `--confirm-slashing` flag. Doing so WILL RESULT in both your ETH bond and your RPL collateral being slashed.") return nil } - if !prompt.ConfirmWithIAgree("\n%sYou have the `--confirm-slashing` flag enabled. Closing this minipool WILL RESULT in the complete loss of your initial ETH bond and enough of your RPL stake to cover the losses to the staking pool. Please confirm you understand this and want to continue closing the minipool.%s", colorRed, colorReset) { + fmt.Println() + if !prompt.ConfirmWithIAgree("%s", color.Red("You have the `--confirm-slashing` flag enabled. Closing this minipool WILL RESULT in the complete loss of your initial ETH bond and enough of your RPL stake to cover the losses to the staking pool. Please confirm you understand this and want to continue closing the minipool.")) { fmt.Println("Cancelled.") return nil } @@ -222,7 +229,7 @@ func closeMinipools(c *cli.Context) error { if distributableBalance.Cmp(yellowThreshold) < 0 { // More than the user deposit balance but less than 31.5, ETH will be slashed with a red warning - if !prompt.ConfirmWithIAgree("%sWARNING: Minipool %s has a distributable balance of %.6f ETH. Closing it in this state WILL RESULT in a loss of ETH. You will only receive %.6f ETH back. Please confirm you understand this and want to continue closing the minipool.%s", colorRed, minipool.Address.Hex(), math.RoundDown(eth.WeiToEth(distributableBalance), 6), math.RoundDown(eth.WeiToEth(minipool.NodeShare), 6), colorReset) { + if !prompt.ConfirmWithIAgree("%s", color.RedSprintf("WARNING: Minipool %s has a distributable balance of %.6f ETH. Closing it in this state WILL RESULT in a loss of ETH. You will only receive %.6f ETH back. Please confirm you understand this and want to continue closing the minipool.", minipool.Address.Hex(), math.RoundDown(eth.WeiToEth(distributableBalance), 6), math.RoundDown(eth.WeiToEth(minipool.NodeShare), 6))) { fmt.Println("Cancelled.") return nil } @@ -231,7 +238,7 @@ func closeMinipools(c *cli.Context) error { } if distributableBalance.Cmp(thirtyTwo) < 0 { // More than 31.5 but less than 32, ETH will be slashed with a yellow warning - if !prompt.Confirm("%sWARNING: Minipool %s has a distributable balance of %.6f ETH. Closing it in this state WILL RESULT in a loss of ETH. You will only receive %.6f ETH back. Please confirm you understand this and want to continue closing the minipool.%s", colorYellow, minipool.Address.Hex(), math.RoundDown(eth.WeiToEth(distributableBalance), 6), math.RoundDown(eth.WeiToEth(minipool.NodeShare), 6), colorReset) { + if !prompt.ConfirmYellow("WARNING: Minipool %s has a distributable balance of %.6f ETH. Closing it in this state WILL RESULT in a loss of ETH. You will only receive %.6f ETH back. Please confirm you understand this and want to continue closing the minipool.", minipool.Address.Hex(), math.RoundDown(eth.WeiToEth(distributableBalance), 6), math.RoundDown(eth.WeiToEth(minipool.NodeShare), 6)) { fmt.Println("Cancelled.") return nil } diff --git a/rocketpool-cli/minipool/distribute.go b/rocketpool-cli/minipool/distribute.go index fd553288e..b42d29508 100644 --- a/rocketpool-cli/minipool/distribute.go +++ b/rocketpool-cli/minipool/distribute.go @@ -16,6 +16,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/rocket-pool/smartnode/shared/utils/math" ) @@ -65,25 +66,31 @@ func distributeBalance(c *cli.Context) error { // Print ineligible ones if len(versionTooLowMinipools) > 0 { - fmt.Printf("%sWARNING: The following minipools are using an old delegate and cannot have their rewards safely distributed:\n", colorYellow) + color.YellowPrintln("WARNING: The following minipools are using an old delegate and cannot have their rewards safely distributed:") for _, mp := range versionTooLowMinipools { - fmt.Printf("\t%s\n", mp.Address) + color.YellowPrintf("\t%s\n", mp.Address) } - fmt.Printf("\nPlease upgrade the delegate for these minipools using `rocketpool minipool delegate-upgrade` in order to distribute their ETH balances.%s\n\n", colorReset) + fmt.Println() + color.YellowPrintln("Please upgrade the delegate for these minipools using `rocketpool minipool delegate-upgrade` in order to distribute their ETH balances.") + fmt.Println() } if len(balanceLessThanRefundMinipools) > 0 { - fmt.Printf("%sWARNING: The following minipools have refunds larger than their current balances and cannot be distributed at this time:\n", colorYellow) + color.YellowPrintln("WARNING: The following minipools have refunds larger than their current balances and cannot be distributed at this time:") for _, mp := range balanceLessThanRefundMinipools { - fmt.Printf("\t%s\n", mp.Address) + color.YellowPrintf("\t%s\n", mp.Address) } - fmt.Printf("\nIf you have recently migrated these minipools from solo validators, please wait until enough rewards have been sent from the Beacon Chain to your minipools to cover your refund amounts.%s\n\n", colorReset) + fmt.Println() + color.YellowPrintln("If you have recently migrated these minipools from solo validators, please wait until enough rewards have been sent from the Beacon Chain to your minipools to cover your refund amounts.") + fmt.Println() } if len(balanceTooBigMinipools) > 0 { - fmt.Printf("%sWARNING: The following minipools have over 8 ETH in their balances (after accounting for refunds):\n", colorYellow) + color.YellowPrintln("WARNING: The following minipools have over 8 ETH in their balances (after accounting for refunds):") for _, mp := range balanceTooBigMinipools { - fmt.Printf("\t%s\n", mp.Address) + color.YellowPrintf("\t%s\n", mp.Address) } - fmt.Printf("\nDistributing these minipools will close them, effectively terminating them. If you're sure you want to do this, please use `rocketpool minipool close` on them instead.%s\n\n", colorReset) + fmt.Println() + color.YellowPrintln("Distributing these minipools will close them, effectively terminating them. If you're sure you want to do this, please use `rocketpool minipool close` on them instead.") + fmt.Println() } if len(eligibleMinipools) == 0 { diff --git a/rocketpool-cli/minipool/exit.go b/rocketpool-cli/minipool/exit.go index 784a9157d..2fef45907 100644 --- a/rocketpool-cli/minipool/exit.go +++ b/rocketpool-cli/minipool/exit.go @@ -10,6 +10,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -86,11 +87,13 @@ func exitMinipools(c *cli.Context) error { } // Show a warning message - fmt.Printf("%sNOTE:\n", colorYellow) - fmt.Println("You are about to exit your minipool. This will tell each one's validator to stop all activities on the Beacon Chain.") - fmt.Println("Please continue to run your validators until each one you've exited has been processed by the exit queue.\nYou can watch their progress on the https://beaconcha.in explorer.") - fmt.Println("Your funds will be locked on the Beacon Chain until they've been withdrawn, which will happen automatically (this may take a few days).") - fmt.Printf("Once your funds have been withdrawn, you can run `rocketpool minipool close` to distribute them to your withdrawal address and close the minipool.\n\n%s", colorReset) + color.YellowPrintln("NOTE:") + color.YellowPrintln("You are about to exit your minipool. This will tell each one's validator to stop all activities on the Beacon Chain.") + color.YellowPrintln("Please continue to run your validators until each one you've exited has been processed by the exit queue.") + color.YellowPrintln("You can watch their progress on the https://beaconcha.in explorer.") + color.YellowPrintln("Your funds will be locked on the Beacon Chain until they've been withdrawn, which will happen automatically (this may take a few days).") + color.YellowPrintln("Once your funds have been withdrawn, you can run `rocketpool minipool close` to distribute them to your withdrawal address and close the minipool.") + fmt.Println() // Prompt for confirmation if !(c.Bool("yes") || prompt.ConfirmWithIAgree("Are you sure you want to exit %d minipool(s)? This action cannot be undone!", len(selectedMinipools))) { diff --git a/rocketpool-cli/minipool/rescue-dissolved.go b/rocketpool-cli/minipool/rescue-dissolved.go index 92ce8d032..ab516af16 100644 --- a/rocketpool-cli/minipool/rescue-dissolved.go +++ b/rocketpool-cli/minipool/rescue-dissolved.go @@ -16,6 +16,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/rocket-pool/smartnode/shared/utils/math" ) @@ -86,25 +87,31 @@ func rescueDissolved(c *cli.Context) error { // Print ineligible ones if len(versionTooLowMinipools) > 0 { - fmt.Printf("%sWARNING: The following minipools are using an old delegate and cannot be safely rescued:\n", colorYellow) + color.YellowPrintf("WARNING: The following minipools are using an old delegate and cannot be safely rescued:\n") for _, mp := range versionTooLowMinipools { - fmt.Printf("\t%s\n", mp.Address) + color.YellowPrintf("\t%s\n", mp.Address) } - fmt.Printf("\nPlease upgrade the delegate for these minipools using `rocketpool minipool delegate-upgrade` before rescuing them.%s\n\n", colorReset) + fmt.Println() + color.YellowPrintln("Please upgrade the delegate for these minipools using `rocketpool minipool delegate-upgrade` before rescuing them.") + fmt.Println() } if len(balanceCompletedMinipools) > 0 { - fmt.Printf("%sNOTE: The following minipools already have 32 ETH or more deposited:\n", colorYellow) + color.YellowPrintf("NOTE: The following minipools already have 32 ETH or more deposited:\n") for _, mp := range balanceCompletedMinipools { - fmt.Printf("\t%s\n", mp.Address) + color.YellowPrintf("\t%s\n", mp.Address) } - fmt.Printf("\nThese minipools don't need to be rescued.%s\n\n", colorReset) + fmt.Println() + color.YellowPrintln("These minipools don't need to be rescued.") + fmt.Println() } if len(invalidBeaconStateMinipools) > 0 { - fmt.Printf("%sNOTE: The following minipools have an invalid state on the Beacon Chain (expected 'initialized_pending'):\n", colorYellow) + color.YellowPrintf("NOTE: The following minipools have an invalid state on the Beacon Chain (expected 'initialized_pending'):\n") for _, mp := range invalidBeaconStateMinipools { - fmt.Printf("\t%s (%s)\n", mp.Address, mp.BeaconState) + color.YellowPrintf("\t%s (%s)\n", mp.Address, mp.BeaconState) } - fmt.Printf("\nThese minipools cannot currently be rescued.%s\n\n", colorReset) + fmt.Println() + color.YellowPrintln("These minipools cannot currently be rescued.") + fmt.Println() } // Check for rescuable minipools @@ -113,7 +120,9 @@ func rescueDissolved(c *cli.Context) error { return nil } - fmt.Printf("%sNOTE: the amounts required for completion below use the validator balances according to the Beacon Chain.\nIf you have recently sent a rescue deposit to this minipool, please wait until it has been registered with the Beacon Chain for these remaining amounts to be accurate.%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: the amounts required for completion below use the validator balances according to the Beacon Chain.") + color.YellowPrintln("If you have recently sent a rescue deposit to this minipool, please wait until it has been registered with the Beacon Chain for these remaining amounts to be accurate.") + fmt.Println() // Get selected minipools var selectedMinipool *api.MinipoolRescueDissolvedDetails @@ -223,7 +232,8 @@ func rescueDissolved(c *cli.Context) error { if _, err = rp.WaitForTransaction(response.TxHash); err != nil { return fmt.Errorf("Could not rescue minipool %s: %s.\n", selectedMinipool.Address.Hex(), err.Error()) } else { - fmt.Printf("Successfully deposited to minipool %s.\nPlease watch its status on a chain explorer such as https://beaconcha.in; it may take up to 24 hours for this deposit to be seen by the chain.\n", selectedMinipool.Address.Hex()) + fmt.Printf("Successfully deposited to minipool %s.\n", selectedMinipool.Address.Hex()) + fmt.Println("Please watch its status on a chain explorer such as https://beaconcha.in; it may take up to 24 hours for this deposit to be seen by the chain.") } // Return diff --git a/rocketpool-cli/minipool/status.go b/rocketpool-cli/minipool/status.go index a3bde6045..f20523ac3 100644 --- a/rocketpool-cli/minipool/status.go +++ b/rocketpool-cli/minipool/status.go @@ -12,14 +12,11 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/hex" "github.com/rocket-pool/smartnode/shared/utils/math" ) -const colorReset string = "\033[0m" -const colorRed string = "\033[31m" -const colorYellow string = "\033[33m" - func getStatus(c *cli.Context) error { // Get RP client @@ -135,7 +132,7 @@ func getStatus(c *cli.Context) error { } if len(minipoolsPastDissolveNotificationThreshold) > 0 { - fmt.Printf("%sAttention! %d minipool(s) are close to being dissolved:\n%s", colorRed, len(minipoolsPastDissolveNotificationThreshold), colorReset) + color.RedPrintf("Attention! %d minipool(s) are close to being dissolved:\n", len(minipoolsPastDissolveNotificationThreshold)) for _, minipool := range minipoolsPastDissolveNotificationThreshold { fmt.Printf("- %s (%s until dissolve)\n", minipool.Address.Hex(), minipool.TimeUntilDissolve) } @@ -157,9 +154,9 @@ func printMinipoolDetails(minipool api.MinipoolDetails, latestDelegate common.Ad if minipool.Penalties == 0 { fmt.Println("Penalties: 0") } else if minipool.Penalties < 3 { - fmt.Printf("%sStrikes: %d%s\n", colorYellow, minipool.Penalties, colorReset) + color.YellowPrintf("Strikes: %d\n", minipool.Penalties) } else { - fmt.Printf("%sInfractions: %d%s\n", colorRed, minipool.Penalties, colorReset) + color.RedPrintf("Infractions: %d\n", minipool.Penalties) } fmt.Printf("Status: %s\n", minipool.Status.Status.String()) fmt.Printf("Status updated: %s\n", minipool.Status.StatusTime.Format(TimeFormat)) @@ -220,7 +217,7 @@ func printMinipoolDetails(minipool api.MinipoolDetails, latestDelegate common.Ad fmt.Printf("Effective delegate: %s\n", cliutils.GetPrettyAddress(minipool.EffectiveDelegate)) if minipool.EffectiveDelegate != latestDelegate { - fmt.Printf("%s*Minipool can be upgraded to delegate %s!%s\n", colorYellow, latestDelegate.Hex(), colorReset) + color.YellowPrintf("*Minipool can be upgraded to delegate %s!\n", latestDelegate.Hex()) } fmt.Printf("\n") diff --git a/rocketpool-cli/network/dao-proposals.go b/rocketpool-cli/network/dao-proposals.go index adb8b9061..bc335c817 100644 --- a/rocketpool-cli/network/dao-proposals.go +++ b/rocketpool-cli/network/dao-proposals.go @@ -10,6 +10,7 @@ import ( "github.com/rocket-pool/smartnode/bindings/utils/eth" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/urfave/cli" ) @@ -40,13 +41,12 @@ func getActiveDAOProposals(c *cli.Context) error { } // Voting status - fmt.Printf("%s=== Snapshot Voting ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Snapshot Voting ===") blankAddress := common.Address{} if snapshotProposalsResponse.SignallingAddress == blankAddress { fmt.Printf("The node does not currently have a snapshot signalling address set.\nTo learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) } else { - fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.", colorBlue, snapshotProposalsResponse.SignallingAddressFormatted, colorReset) - fmt.Println() + fmt.Println("The node has a signalling address of", color.LightBlue(snapshotProposalsResponse.SignallingAddressFormatted), "which can represent it when voting on Rocket Pool Snapshot governance proposals.") } voteCount := snapshotProposalsResponse.SnapshotResponse.VoteCount() @@ -120,12 +120,12 @@ func getActiveDAOProposals(c *cli.Context) error { votedChoices = fmt.Sprintf("%v", proposalVote.Choice) } - fmt.Printf("%s%s voted [%s] on this proposal\n%s", colorGreen, voter, votedChoices, colorReset) + color.GreenPrintf("%s voted [%s] on this proposal\n", voter, votedChoices) voted = true } } if !voted { - fmt.Printf("%sYou have NOT voted on this proposal yet\n%s", colorYellow, colorReset) + color.YellowPrintln("You have NOT voted on this proposal yet") } } @@ -133,7 +133,7 @@ func getActiveDAOProposals(c *cli.Context) error { fmt.Println() // Onchain Voting Status - fmt.Printf("%s=== Onchain Voting ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Onchain Voting ===") switch snapshotProposalsResponse.OnchainVotingDelegate { case blankAddress: @@ -141,7 +141,7 @@ func getActiveDAOProposals(c *cli.Context) error { case snapshotProposalsResponse.AccountAddress: fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals. You can have another node represent you by running `rocketpool p svd
`.") default: - fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.\n", colorBlue, snapshotProposalsResponse.OnchainVotingDelegateFormatted, colorReset) + fmt.Println("The node has a voting delegate of", color.LightBlue(snapshotProposalsResponse.OnchainVotingDelegateFormatted), "which can represent it when voting on Rocket Pool onchain governance proposals.") } fmt.Printf("The node's local voting power: %.10f\n", eth.WeiToEth(snapshotProposalsResponse.VotingPower)) diff --git a/rocketpool-cli/network/generate-tree.go b/rocketpool-cli/network/generate-tree.go index 50b92e4a1..1a08ea33c 100644 --- a/rocketpool-cli/network/generate-tree.go +++ b/rocketpool-cli/network/generate-tree.go @@ -5,15 +5,12 @@ import ( "strconv" "github.com/rocket-pool/smartnode/shared/services/rocketpool" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) const ( - colorReset string = "\033[0m" - colorGreen string = "\033[32m" - colorYellow string = "\033[33m" - signallingAddressLink string = "https://docs.rocketpool.net/pdao/participate#setting-your-snapshot-signalling-address" ) @@ -35,9 +32,16 @@ func generateRewardsTree(c *cli.Context) error { // Print archive node info archiveEcUrl := cfg.Smartnode.ArchiveECUrl.Value.(string) if archiveEcUrl == "" { - fmt.Printf("%sNOTE: in order to generate a Merkle rewards tree for a past rewards interval, you will likely need to have access to an Execution client with archival state.\nBy default, your Smart Node's Execution client will not provide this.\n\nPlease specify the URL of an archive-capable EC in the Smart Node section of the `rocketpool service config` Terminal UI.\nIf you need one, Alchemy provides a free service which you can use: https://www.alchemy.com/ethereum%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: in order to generate a Merkle rewards tree for a past rewards interval, you will likely need to have access to an Execution client with archival state.") + color.YellowPrintln("By default, your Smart Node's Execution client will not provide this.") + fmt.Println() + color.YellowPrintln("Please specify the URL of an archive-capable EC in the Smart Node section of the `rocketpool service config` Terminal UI.") + fmt.Println() + color.YellowPrintln("If you need one, Alchemy provides a free service which you can use: https://www.alchemy.com/ethereum") + fmt.Println() } else { - fmt.Printf("%sYou have an archive EC specified at [%s]. This will be used for tree generation.%s\n\n", colorGreen, archiveEcUrl, colorReset) + color.GreenPrintln("You have an archive EC specified at [%s]. This will be used for tree generation.", archiveEcUrl) + fmt.Println() } // Get the index @@ -77,7 +81,8 @@ func generateRewardsTree(c *cli.Context) error { return err } - fmt.Printf("Your request to generate the rewards tree for interval %d has been applied, and your `watchtower` container will begin the process during its next duty check (typically 5 minutes).\nYou can follow its progress with %s`rocketpool service logs watchtower`%s.\n\n", index, colorGreen, colorReset) + fmt.Printf("Your request to generate the rewards tree for interval %d has been applied, and your `watchtower` container will begin the process during its next duty check (typically 5 minutes).\n", index) + fmt.Println("You can follow its progress with", color.Green("`rocketpool service logs watchtower`.")) if c.Bool("yes") || prompt.Confirm("Would you like to restart the watchtower container now, so it starts generating the file immediately?") { container := fmt.Sprintf("%s_watchtower", cfg.Smartnode.ProjectName.Value.(string)) diff --git a/rocketpool-cli/network/stats.go b/rocketpool-cli/network/stats.go index 4be549579..1273beb4c 100644 --- a/rocketpool-cli/network/stats.go +++ b/rocketpool-cli/network/stats.go @@ -6,10 +6,7 @@ import ( "github.com/urfave/cli" "github.com/rocket-pool/smartnode/shared/services/rocketpool" -) - -const ( - colorBlue string = "\033[36m" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) func getStats(c *cli.Context) error { @@ -36,13 +33,14 @@ func getStats(c *cli.Context) error { response.DissolvedMinipoolCount // Print & return - fmt.Printf("%s========== General Stats ==========%s\n", colorGreen, colorReset) + color.GreenPrintln("========== General Stats ==========") fmt.Printf("Total Value Locked: %f ETH\n", response.TotalValueLocked) fmt.Printf("Deposit Pool Balance: %f ETH\n", response.DepositPoolBalance) fmt.Printf("Minipool Queue Demand: %f ETH\n", response.MinipoolCapacity) - fmt.Printf("Deposit Pool ETH Used: %f%%\n\n", response.StakerUtilization*100) + fmt.Printf("Deposit Pool ETH Used: %f%%\n", response.StakerUtilization*100) + fmt.Println() - fmt.Printf("%s============== Nodes ==============%s\n", colorGreen, colorReset) + color.GreenPrintln("============== Nodes ==============") fmt.Printf("Current Commission Rate: %f%%\n", response.NodeFee*100) fmt.Printf("Node Count: %d\n", response.NodeCount) fmt.Printf("Active Minipools: %d\n", activeMinipools) @@ -51,9 +49,10 @@ func getStats(c *cli.Context) error { fmt.Printf(" Staking: %d\n", response.StakingMinipoolCount) fmt.Printf(" Withdrawable: %d\n", response.WithdrawableMinipoolCount) fmt.Printf(" Dissolved: %d\n", response.DissolvedMinipoolCount) - fmt.Printf("Finalized Minipools: %d\n\n", response.FinalizedMinipoolCount) + fmt.Printf("Finalized Minipools: %d\n", response.FinalizedMinipoolCount) + fmt.Println() - fmt.Printf("%s=========== Megapools ============%s\n", colorGreen, colorReset) + color.GreenPrintln("=========== Megapools ============") fmt.Printf("Megapool contracts deployed: %d\n", response.MegapoolContractCount) fmt.Printf("Total megapool validators: %d\n", response.MegapoolValidatorCount) fmt.Printf(" Staking: %d\n", response.MegapoolValidatorStakingCount) @@ -62,19 +61,22 @@ func getStats(c *cli.Context) error { fmt.Printf(" Exited: %d\n", response.MegapoolValidatorExitedCount) fmt.Printf(" Locked: %d\n", response.MegapoolValidatorLockedCount) fmt.Printf(" Exiting: %d\n", response.MegapoolValidatorExitingCount) - fmt.Printf(" Dissolved: %d\n\n", response.MegapoolValidatorDissolvedCount) + fmt.Printf(" Dissolved: %d\n", response.MegapoolValidatorDissolvedCount) + fmt.Println() - fmt.Printf("%s========== Smoothing Pool =========%s\n", colorGreen, colorReset) - fmt.Printf("Contract Address: %s%s%s\n", colorBlue, response.SmoothingPoolAddress.Hex(), colorReset) + color.GreenPrintln("========== Smoothing Pool ==========") + fmt.Printf("Contract Address: %s\n", color.LightBlue(response.SmoothingPoolAddress.Hex())) fmt.Printf("Nodes Opted in: %d\n", response.SmoothingPoolNodes) - fmt.Printf("Pending Balance: %f\n\n", response.SmoothingPoolBalance) + fmt.Printf("Pending Balance: %f\n", response.SmoothingPoolBalance) + fmt.Println() - fmt.Printf("%s============== Tokens =============%s\n", colorGreen, colorReset) + color.GreenPrintln("============== Tokens ==============") fmt.Printf("rETH Price (ETH / rETH): %f ETH\n", response.RethPrice) fmt.Printf("RPL Price (ETH / RPL): %f ETH\n", response.RplPrice) fmt.Printf("Total RPL staked: %f RPL\n", response.TotalRplStaked) fmt.Printf("Total Megapool RPL staked: %f RPL\n", response.TotalMegapoolRplStaked) fmt.Printf("Total Legacy RPL staked: %f RPL\n", response.TotalLegacyRplStaked) + fmt.Println() return nil diff --git a/rocketpool-cli/node/claim-rewards.go b/rocketpool-cli/node/claim-rewards.go index c012b488a..469311121 100644 --- a/rocketpool-cli/node/claim-rewards.go +++ b/rocketpool-cli/node/claim-rewards.go @@ -16,13 +16,10 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) -const ( - colorBlue string = "\033[36m" -) - func nodeClaimRewards(c *cli.Context) error { // Get RP client @@ -33,7 +30,9 @@ func nodeClaimRewards(c *cli.Context) error { defer rp.Close() // Provide a notice - fmt.Printf("%sWelcome to the new rewards system!\nYou no longer need to claim rewards at each interval - you can simply let them accumulate and claim them whenever you want.\nHere you can see which intervals you haven't claimed yet, and how many rewards you earned during each one.%s\n", colorBlue, colorReset) + color.LightBluePrintln("Welcome to the new rewards system!") + color.LightBluePrintln("You no longer need to claim rewards at each interval - you can simply let them accumulate and claim them whenever you want.") + color.LightBluePrintln("Here you can see which intervals you haven't claimed yet, and how many rewards you earned during each one.") fmt.Println() // Get eligible intervals @@ -43,7 +42,7 @@ func nodeClaimRewards(c *cli.Context) error { } if !rewardsInfoResponse.Registered { - fmt.Printf("This node is not currently registered.\n") + fmt.Println("This node is not currently registered.") return nil } @@ -63,7 +62,7 @@ func nodeClaimRewards(c *cli.Context) error { // Download the Merkle trees for all unclaimed intervals that don't exist if len(missingIntervals) > 0 || len(invalidIntervals) > 0 { fmt.Println() - fmt.Printf("%sNOTE: If you would like to regenerate these tree files manually, please answer `n` to the prompt below and run `rocketpool network generate-rewards-tree` before claiming your rewards.%s\n", colorBlue, colorReset) + color.LightBluePrintln("NOTE: If you would like to regenerate these tree files manually, please answer `n` to the prompt below and run `rocketpool network generate-rewards-tree` before claiming your rewards.") if !prompt.Confirm("Would you like to download all missing rewards tree files now?") { fmt.Println("Cancelled.") return nil diff --git a/rocketpool-cli/node/claim-unclaimed-rewards.go b/rocketpool-cli/node/claim-unclaimed-rewards.go index 493a60d6a..fe2fdb0f7 100644 --- a/rocketpool-cli/node/claim-unclaimed-rewards.go +++ b/rocketpool-cli/node/claim-unclaimed-rewards.go @@ -8,6 +8,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/rocket-pool/smartnode/shared/utils/math" "github.com/urfave/cli" @@ -32,8 +33,8 @@ func claimUnclaimedRewards(c *cli.Context) error { fmt.Printf("The node's withdrawal address is %s\n", status.PrimaryWithdrawalAddress) if status.UnclaimedRewards != nil && status.UnclaimedRewards.Cmp(big.NewInt(0)) > 0 { fmt.Printf("You have %.6f ETH in unclaimed rewards.\n", math.RoundDown(eth.WeiToEth(status.UnclaimedRewards), 6)) - fmt.Printf("Your node %s%s%s's rewards were distributed, but the withdrawal address (at the time of distribution) was unable to accept ETH. ", - colorBlue, status.AccountAddress, colorReset) + fmt.Printf("Your node %s's rewards were distributed, but the withdrawal address (at the time of distribution) was unable to accept ETH. ", + color.LightBlue(status.AccountAddress.String())) fmt.Println("Before continuing, please use the command `rocketpool node set-primary-withdrawal-address` to configure an address that can accept ETH") } else { fmt.Println("You have no unclaimed rewards.") diff --git a/rocketpool-cli/node/primary-withdrawal-address.go b/rocketpool-cli/node/primary-withdrawal-address.go index 5ea1d21fc..39ce420e4 100644 --- a/rocketpool-cli/node/primary-withdrawal-address.go +++ b/rocketpool-cli/node/primary-withdrawal-address.go @@ -11,6 +11,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -41,9 +42,6 @@ func setPrimaryWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string) } // Print the "pending" disclaimer - colorReset := "\033[0m" - colorRed := "\033[31m" - colorYellow := "\033[33m" var confirm bool fmt.Println("You are about to change your primary withdrawal address. All future ETH rewards/refunds will be sent there.\nIf you haven't set your RPL withdrawal address, RPL rewards will be sent there as well.") if !c.Bool("force") { @@ -51,11 +49,13 @@ func setPrimaryWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string) fmt.Println("By default, this will put your new primary withdrawal address into a \"pending\" state.") fmt.Println("Rocket Pool will continue to use your old primary withdrawal address until you confirm that you own the new address via the Rocket Pool website.") fmt.Println("You will need to use a web3-compatible wallet (such as MetaMask) with your new address to confirm it.") - fmt.Printf("%sIf you cannot use such a wallet, or if you want to bypass this step and force Rocket Pool to use the new address immediately, please re-run this command with the \"--force\" flag.\n\n%s", colorYellow, colorReset) + color.YellowPrintln("If you cannot use such a wallet, or if you want to bypass this step and force Rocket Pool to use the new address immediately, please re-run this command with the \"--force\" flag.") + fmt.Println() } else { confirm = true - fmt.Printf("%sYou have specified the \"--force\" option, so your new address will take effect immediately.\n", colorRed) - fmt.Printf("Please ensure that you have the correct address - if you do not control the new address, you will not be able to change this once set!%s\n\n", colorReset) + color.RedPrintln("You have specified the \"--force\" option, so your new address will take effect immediately.") + color.RedPrintln("Please ensure that you have the correct address - if you do not control the new address, you will not be able to change this once set!") + fmt.Println() } // Check if the withdrawal address can be set diff --git a/rocketpool-cli/node/rewards.go b/rocketpool-cli/node/rewards.go index 7d45bd3e3..83a3b0113 100644 --- a/rocketpool-cli/node/rewards.go +++ b/rocketpool-cli/node/rewards.go @@ -11,6 +11,7 @@ import ( rprewards "github.com/rocket-pool/smartnode/shared/services/rewards" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -50,7 +51,7 @@ func getRewards(c *cli.Context) error { // Download the Merkle trees for all unclaimed intervals that don't exist if len(missingIntervals) > 0 || len(invalidIntervals) > 0 { fmt.Println() - fmt.Printf("%sNOTE: If you would like to regenerate these tree files manually, please answer `n` to the prompt below and run `rocketpool network generate-rewards-tree` before claiming your rewards.%s\n", colorBlue, colorReset) + color.LightBluePrintln("NOTE: If you would like to regenerate these tree files manually, please answer `n` to the prompt below and run `rocketpool network generate-rewards-tree` before claiming your rewards.") if !prompt.Confirm("Would you like to download all missing rewards tree files now?") { fmt.Println("Cancelled.") return nil diff --git a/rocketpool-cli/node/rpl-withdrawal-address.go b/rocketpool-cli/node/rpl-withdrawal-address.go index 714429789..542d71c88 100644 --- a/rocketpool-cli/node/rpl-withdrawal-address.go +++ b/rocketpool-cli/node/rpl-withdrawal-address.go @@ -12,6 +12,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -42,9 +43,6 @@ func setRPLWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string) erro } // Print the "pending" disclaimer - colorReset := "\033[0m" - colorRed := "\033[31m" - colorYellow := "\033[33m" var confirm bool fmt.Println("You are about to change your RPL withdrawal address. All future RPL rewards/refunds will be sent there.") if !c.Bool("force") { @@ -52,11 +50,13 @@ func setRPLWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string) erro fmt.Println("By default, this will put your new RPL withdrawal address into a \"pending\" state.") fmt.Println("Rocket Pool will continue to use your old RPL withdrawal address (or your primary withdrawal address if your RPL withdrawal address has not been set) until you confirm that you own the new address via the Rocket Pool website.") fmt.Println("You will need to use a web3-compatible wallet (such as MetaMask) with your new address to confirm it.") - fmt.Printf("%sIf you cannot use such a wallet, or if you want to bypass this step and force Rocket Pool to use the new address immediately, please re-run this command with the \"--force\" flag.\n\n%s", colorYellow, colorReset) + color.YellowPrintln("If you cannot use such a wallet, or if you want to bypass this step and force Rocket Pool to use the new address immediately, please re-run this command with the \"--force\" flag.") + fmt.Println() } else { confirm = true - fmt.Printf("%sYou have specified the \"--force\" option, so your new address will take effect immediately.\n", colorRed) - fmt.Printf("Please ensure that you have the correct address - if you do not control the new address, you will not be able to change this once set!%s\n\n", colorReset) + color.RedPrintln("You have specified the \"--force\" option, so your new address will take effect immediately.") + color.RedPrintln("Please ensure that you have the correct address - if you do not control the new address, you will not be able to change this once set!") + fmt.Println() } // Check if the withdrawal address can be set @@ -123,7 +123,7 @@ func setRPLWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string) erro // Prompt for confirmation if canResponse.RPLStake.Cmp(common.Big0) == 1 { - fmt.Printf("%sNOTE: You currently have %.6f RPL staked. Withdrawing it will *no longer* send it to your primary withdrawal address. It will be sent to the new RPL withdrawal address instead. Please verify you have control over that address before confirming this!%s\n", colorYellow, eth.WeiToEth(canResponse.RPLStake), colorReset) + color.YellowPrintf("NOTE: You currently have %.6f RPL staked. Withdrawing it will *no longer* send it to your primary withdrawal address. It will be sent to the new RPL withdrawal address instead. Please verify you have control over that address before confirming this!\n", eth.WeiToEth(canResponse.RPLStake)) } if !(c.Bool("yes") || prompt.Confirm("Are you sure you want to set your node's RPL withdrawal address to %s?", withdrawalAddressString)) { fmt.Println("Cancelled.") diff --git a/rocketpool-cli/node/send.go b/rocketpool-cli/node/send.go index 37766e2d8..eaab33cd1 100644 --- a/rocketpool-cli/node/send.go +++ b/rocketpool-cli/node/send.go @@ -10,6 +10,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -70,7 +71,8 @@ func nodeSend(c *cli.Context, amountRaw float64, sendAll bool, token string, toA fmt.Printf("Token name: %s\n", canSend.TokenName) fmt.Printf("Token symbol: %s\n", canSend.TokenSymbol) fmt.Printf("Node balance: %.8f %s\n\n", canSend.Balance, canSend.TokenSymbol) - fmt.Printf("%sWARNING: Please confirm that the above token is the one you intend to send before confirming below!%s\n\n", colorYellow, colorReset) + color.YellowPrintln("WARNING: Please confirm that the above token is the one you intend to send before confirming below!") + fmt.Println() if !(c.Bool("yes") || prompt.Confirm("Are you sure you want to send %.8f of %s to %s? This action cannot be undone!", amountRaw, tokenString, toAddressString)) { fmt.Println("Cancelled.") @@ -188,7 +190,8 @@ func nodeSendAll(c *cli.Context, rp *rocketpool.Client, token string, toAddress fmt.Printf("Token name: %s\n", canSend.TokenName) fmt.Printf("Token symbol: %s\n", canSend.TokenSymbol) fmt.Printf("Node balance: %.8f %s\n\n", canSend.Balance, canSend.TokenSymbol) - fmt.Printf("%sWARNING: Please confirm that the above token is the one you intend to send before confirming below!%s\n\n", colorYellow, colorReset) + color.YellowPrintln("WARNING: Please confirm that the above token is the one you intend to send before confirming below!") + fmt.Println() if !(c.Bool("yes") || prompt.Confirm("Are you sure you want to send all %.8f of %s to %s? This action cannot be undone!", amountRaw, tokenString, toAddressString)) { fmt.Println("Cancelled.") diff --git a/rocketpool-cli/node/smoothing-pool.go b/rocketpool-cli/node/smoothing-pool.go index a2913ae07..358eeab63 100644 --- a/rocketpool-cli/node/smoothing-pool.go +++ b/rocketpool-cli/node/smoothing-pool.go @@ -8,6 +8,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -56,7 +57,9 @@ func joinSmoothingPool(c *cli.Context) error { return err } - fmt.Printf("%sNOTE: This process will restart your node's validator client.\nYou may miss an attestation if you are currently scheduled to produce one.%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: This process will restart your node's validator client.") + color.YellowPrintln("You may miss an attestation if you are currently scheduled to produce one.") + fmt.Println() // Prompt for confirmation if !(c.Bool("yes") || prompt.Confirm("Are you sure you want to join the Smoothing Pool?")) { @@ -147,7 +150,9 @@ func leaveSmoothingPool(c *cli.Context) error { // Log & return fmt.Println("Successfully left the Smoothing Pool.") - fmt.Printf("%sNOTE: Your validator client will restart to change its fee recipient back to your node's distributor once the next Epoch has been finalized.\nYou may miss an attestation when this happens (or multiple if you have Doppelganger Protection enabled); this is normal.%s\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: Your validator client will restart to change its fee recipient back to your node's distributor once the next Epoch has been finalized.") + color.YellowPrintln("You may miss an attestation when this happens (or multiple if you have Doppelganger Protection enabled); this is normal.") + fmt.Println() return nil } diff --git a/rocketpool-cli/node/status.go b/rocketpool-cli/node/status.go index 6d6f0cd3a..06e83b23a 100644 --- a/rocketpool-cli/node/status.go +++ b/rocketpool-cli/node/status.go @@ -15,14 +15,11 @@ import ( "github.com/rocket-pool/smartnode/addons/rescue_node" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/math" ) const ( - colorReset string = "\033[0m" - colorRed string = "\033[31m" - colorGreen string = "\033[32m" - colorYellow string = "\033[33m" smoothingPoolLink string = "https://docs.rocketpool.net/upgrades/redstone/whats-new#smoothing-pool" signallingAddressLink string = "https://docs.rocketpool.net/pdao/participate#setting-your-snapshot-signalling-address" ) @@ -79,12 +76,10 @@ func getStatus(c *cli.Context) error { } // Account address & balances - fmt.Printf("%s=== Account and Balances ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Account and Balances ===") fmt.Printf( - "The node %s%s%s has a balance of %.6f ETH, %.6f RPL, and %.6f rETH.\n", - colorBlue, - status.AccountAddressFormatted, - colorReset, + "The node %s has a balance of %.6f ETH, %.6f RPL, and %.6f rETH.\n", + color.LightBlue(status.AccountAddressFormatted), math.RoundDown(eth.WeiToEth(status.AccountBalances.ETH), 6), math.RoundDown(eth.WeiToEth(status.AccountBalances.RPL), 6), math.RoundDown(eth.WeiToEth(status.AccountBalances.RETH), 6)) @@ -108,19 +103,15 @@ func getStatus(c *cli.Context) error { } fmt.Println() - fmt.Printf("%s=== Megapool ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Megapool ===") if status.MegapoolDeployed { - fmt.Printf("The node has a megapool deployed at %s%s%s.", colorBlue, status.MegapoolAddress.Hex(), colorReset) - fmt.Println() - fmt.Printf("The megapool has %d validators.", status.MegapoolActiveValidatorCount) - fmt.Println() + fmt.Printf("The node has a megapool deployed at %s.\n", color.LightBlue(status.MegapoolAddress.Hex())) + fmt.Printf("The megapool has %d validators.\n", status.MegapoolActiveValidatorCount) if status.MegapoolNodeDebt.Cmp(big.NewInt(0)) > 0 { - fmt.Printf("The megapool debt is %.6f ETH.", math.RoundDown(eth.WeiToEth(status.MegapoolNodeDebt), 6)) - fmt.Println() + fmt.Printf("The megapool debt is %.6f ETH.\n", math.RoundDown(eth.WeiToEth(status.MegapoolNodeDebt), 6)) } if status.MegapoolRefundValue.Cmp(big.NewInt(0)) > 0 { - fmt.Printf("The megapool refund value is %.6f ETH.", math.RoundDown(eth.WeiToEth(status.MegapoolRefundValue), 6)) - fmt.Println() + fmt.Printf("The megapool refund value is %.6f ETH.\n", math.RoundDown(eth.WeiToEth(status.MegapoolRefundValue), 6)) } } else { fmt.Println("The node does not have a megapool deployed yet.") @@ -129,14 +120,14 @@ func getStatus(c *cli.Context) error { if status.ExpressTicketsProvisioned { fmt.Printf("The node has %d express queue ticket(s).", status.ExpressTicketCount) } else { - fmt.Printf("%sThe node has unprovisioned express queue ticket(s). Please provision them using the `rocketpool node provision-express-tickets` command. You are eligible for %d express tickets.%s", colorYellow, status.ExpressTicketCount, colorReset) + color.YellowPrintf("The node has unprovisioned express queue ticket(s). Please provision them using the `rocketpool node provision-express-tickets` command. You are eligible for %d express tickets.", status.ExpressTicketCount) } fmt.Println() fmt.Println() // Penalties - fmt.Printf("%s=== Penalty Status ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Penalty Status ===") if len(status.PenalizedMinipools) > 0 { strikeMinipools := []common.Address{} infractionMinipools := []common.Address{} @@ -152,11 +143,10 @@ func getStatus(c *cli.Context) error { sort.Slice(strikeMinipools, func(i, j int) bool { // Sort them lexicographically return strikeMinipools[i].Hex() < strikeMinipools[j].Hex() }) - fmt.Printf("%sWARNING: The following minipools have been given strikes for cheating with an invalid fee recipient:\n", colorYellow) + color.YellowPrintln("WARNING: The following minipools have been given strikes for cheating with an invalid fee recipient:") for _, mp := range strikeMinipools { - fmt.Printf("\t%s: %d strikes\n", mp.Hex(), status.PenalizedMinipools[mp]) + color.YellowPrintf("\t%s: %d strikes\n", mp.Hex(), status.PenalizedMinipools[mp]) } - fmt.Println(colorReset) fmt.Println() } @@ -164,11 +154,10 @@ func getStatus(c *cli.Context) error { sort.Slice(infractionMinipools, func(i, j int) bool { // Sort them lexicographically return infractionMinipools[i].Hex() < infractionMinipools[j].Hex() }) - fmt.Printf("%sWARNING: The following minipools have been given infractions for cheating with an invalid fee recipient:\n", colorRed) + color.RedPrintln("WARNING: The following minipools have been given infractions for cheating with an invalid fee recipient:") for _, mp := range infractionMinipools { - fmt.Printf("\t%s: %d infractions\n", mp.Hex(), status.PenalizedMinipools[mp]-2) + color.RedPrintf("\t%s: %d infractions\n", mp.Hex(), status.PenalizedMinipools[mp]-2) } - fmt.Println(colorReset) fmt.Println() } } else { @@ -177,12 +166,13 @@ func getStatus(c *cli.Context) error { } // Signalling Status - fmt.Printf("%s=== Signalling on Snapshot ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Signalling on Snapshot ===") blankAddress := common.Address{} if status.SignallingAddress == blankAddress { - fmt.Printf("The node does not currently have a snapshot signalling address set.\nTo learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) + fmt.Println("The node does not currently have a snapshot signalling address set.") + fmt.Printf("To learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) } else { - fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", colorBlue, status.SignallingAddressFormatted, colorReset) + fmt.Println("The node has a signalling address of", color.LightBlue(status.SignallingAddressFormatted), "which can represent it when voting on Rocket Pool Snapshot governance proposals.") } if status.SnapshotResponse.Error != "" { @@ -206,14 +196,14 @@ func getStatus(c *cli.Context) error { } // Onchain voting status - fmt.Printf("%s=== Onchain Voting ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Onchain Voting ===") if status.OnchainVotingDelegate == blankAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals after it initializes voting.") } else if status.OnchainVotingDelegate == status.AccountAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals. You can have another node represent you by running `rocketpool p svd
`.") } else { - fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.\n", colorBlue, status.OnchainVotingDelegateFormatted, colorReset) + fmt.Println("The node has a voting delegate of", color.LightBlue(status.OnchainVotingDelegateFormatted), "which can represent it when voting on Rocket Pool onchain governance proposals.") } if status.IsRPLLockingAllowed { fmt.Print("The node is allowed to lock RPL to create governance proposals/challenges.\n") @@ -228,80 +218,75 @@ func getStatus(c *cli.Context) error { fmt.Println("") // Primary withdrawal address & balances - fmt.Printf("%s=== Primary Withdrawal Address ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Primary Withdrawal Address ===") if !bytes.Equal(status.AccountAddress.Bytes(), status.PrimaryWithdrawalAddress.Bytes()) { fmt.Printf( - "The node's primary withdrawal address %s%s%s has a balance of %.6f ETH and %.6f RPL.\n", - colorBlue, - status.PrimaryWithdrawalAddressFormatted, - colorReset, + "The node's primary withdrawal address %s has a balance of %.6f ETH and %.6f RPL.\n", + color.LightBlue(status.PrimaryWithdrawalAddressFormatted), math.RoundDown(eth.WeiToEth(status.PrimaryWithdrawalBalances.ETH), 6), math.RoundDown(eth.WeiToEth(status.PrimaryWithdrawalBalances.RPL), 6)) } else { - fmt.Printf("%sThe node's primary withdrawal address has not been changed, so ETH rewards and minipool withdrawals will be sent to the node itself.\n", colorYellow) - fmt.Printf("Consider changing this to a cold wallet address that you control using the `set-withdrawal-address` command.\n%s", colorReset) + color.YellowPrintln("The node's primary withdrawal address has not been changed, so ETH rewards and minipool withdrawals will be sent to the node itself.") + color.YellowPrintln("Consider changing this to a cold wallet address that you control using the `set-withdrawal-address` command.") } fmt.Println("") if status.PendingPrimaryWithdrawalAddress.Hex() != blankAddress.Hex() { - fmt.Printf("%sThe node's primary withdrawal address has a pending change to %s which has not been confirmed yet.\n", colorYellow, status.PendingPrimaryWithdrawalAddressFormatted) - fmt.Printf("Please visit the Rocket Pool website with a web3-compatible wallet to complete this change.%s\n", colorReset) + color.YellowPrintf("The node's primary withdrawal address has a pending change to %s which has not been confirmed yet.\n", status.PendingPrimaryWithdrawalAddressFormatted) + color.YellowPrintln("Please visit the Rocket Pool website with a web3-compatible wallet to complete this change.") fmt.Println("") } // RPL withdrawal address & balances - fmt.Printf("%s=== RPL Withdrawal Address ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== RPL Withdrawal Address ===") if !status.IsRPLWithdrawalAddressSet { - fmt.Printf("The node's RPL withdrawal address has not been set. All RPL rewards will be sent to the primary withdrawal address.\n") + fmt.Println("The node's RPL withdrawal address has not been set. All RPL rewards will be sent to the primary withdrawal address.") } else if bytes.Equal(status.AccountAddress.Bytes(), status.RPLWithdrawalAddress.Bytes()) { - fmt.Printf("The node's RPL withdrawal address has been explicitly set to the node address itself (%s%s%s).\n", colorBlue, status.RPLWithdrawalAddressFormatted, colorReset) + fmt.Printf("The node's RPL withdrawal address has been explicitly set to the node address itself (%s).\n", color.LightBlue(status.RPLWithdrawalAddressFormatted)) } else if bytes.Equal(status.PrimaryWithdrawalAddress.Bytes(), status.RPLWithdrawalAddress.Bytes()) { - fmt.Printf("The node's RPL withdrawal address has been explicitly set to the primary withdrawal address (%s%s%s).\n", colorBlue, status.RPLWithdrawalAddressFormatted, colorReset) + fmt.Printf("The node's RPL withdrawal address has been explicitly set to the primary withdrawal address (%s).\n", color.LightBlue(status.RPLWithdrawalAddressFormatted)) } else { fmt.Printf( - "The node's RPL withdrawal address %s%s%s has a balance of %.6f ETH and %.6f RPL.\n", - colorBlue, - status.RPLWithdrawalAddressFormatted, - colorReset, + "The node's RPL withdrawal address %s has a balance of %.6f ETH and %.6f RPL.\n", + color.LightBlue(status.RPLWithdrawalAddressFormatted), math.RoundDown(eth.WeiToEth(status.RPLWithdrawalBalances.ETH), 6), math.RoundDown(eth.WeiToEth(status.RPLWithdrawalBalances.RPL), 6)) } fmt.Println("") if status.PendingRPLWithdrawalAddress.Hex() != blankAddress.Hex() { - fmt.Printf("%sThe node's RPL withdrawal address has a pending change to %s which has not been confirmed yet.\n", colorYellow, status.PendingRPLWithdrawalAddressFormatted) - fmt.Printf("Please visit the Rocket Pool website with a web3-compatible wallet to complete this change.%s\n", colorReset) + color.YellowPrintf("The node's RPL withdrawal address has a pending change to %s which has not been confirmed yet.\n", status.PendingRPLWithdrawalAddressFormatted) + color.YellowPrintln("Please visit the Rocket Pool website with a web3-compatible wallet to complete this change.") fmt.Println("") } // Fee distributor details - fmt.Printf("%s=== Fee Distributor and Smoothing Pool ===%s\n", colorGreen, colorReset) - fmt.Printf("The node's fee distributor %s%s%s has a balance of %.6f ETH.\n", colorBlue, status.FeeRecipientInfo.FeeDistributorAddress.Hex(), colorReset, math.RoundDown(eth.WeiToEth(status.FeeDistributorBalance), 6)) + color.GreenPrintln("=== Fee Distributor and Smoothing Pool ===") + fmt.Printf("The node's fee distributor %s has a balance of %.6f ETH.\n", color.LightBlue(status.FeeRecipientInfo.FeeDistributorAddress.Hex()), math.RoundDown(eth.WeiToEth(status.FeeDistributorBalance), 6)) if cfg.IsNativeMode && !status.FeeRecipientInfo.IsInSmoothingPool && !status.FeeRecipientInfo.IsInOptOutCooldown { - fmt.Printf("%sNOTE: You are in Native Mode; you MUST ensure that your Validator Client is using this address as its fee recipient!%s\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: You are in Native Mode; you MUST ensure that your Validator Client is using this address as its fee recipient!") } if !status.IsFeeDistributorInitialized { - fmt.Printf("\n%sThe fee distributor hasn't been initialized yet. When you are able, please initialize it with `rocketpool node initialize-fee-distributor`.%s\n", colorYellow, colorReset) + fmt.Println() + color.YellowPrintln("The fee distributor hasn't been initialized yet. When you are able, please initialize it with `rocketpool node initialize-fee-distributor`.") } if status.FeeRecipientInfo.IsInSmoothingPool { fmt.Printf( - "The node is currently opted into the Smoothing Pool (%s%s%s).\n", - colorBlue, - status.FeeRecipientInfo.SmoothingPoolAddress.Hex(), - colorReset) + "The node is currently opted into the Smoothing Pool (%s).\n", + color.LightBlue(status.FeeRecipientInfo.SmoothingPoolAddress.Hex()), + ) if cfg.IsNativeMode { - fmt.Printf("%sNOTE: You are in Native Mode; you MUST ensure that your Validator Client is using this address as its fee recipient!%s\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: You are in Native Mode; you MUST ensure that your Validator Client is using this address as its fee recipient!") } } else if status.FeeRecipientInfo.IsInOptOutCooldown { - fmt.Printf( - "The node is currently opting out of the Smoothing Pool, but cannot safely change its fee recipient yet.\nIt must remain the Smoothing Pool's address (%s%s%s) until the opt-out process is complete.\nIt can safely be changed once Epoch %d is finalized on the Beacon Chain.\n", - colorBlue, - status.FeeRecipientInfo.SmoothingPoolAddress.Hex(), - colorReset, - status.FeeRecipientInfo.OptOutEpoch) + fmt.Println("The node is currently opting out of the Smoothing Pool, but cannot safely change its fee recipient yet.") + fmt.Println("") + fmt.Printf("It must remain the Smoothing Pool's address (%s) until the opt-out process is complete.\n", color.LightBlue(status.FeeRecipientInfo.SmoothingPoolAddress.Hex())) + fmt.Printf("It can safely be changed once Epoch %d is finalized on the Beacon Chain.\n", status.FeeRecipientInfo.OptOutEpoch) if cfg.IsNativeMode { - fmt.Printf("%sNOTE: You are in Native Mode; you MUST ensure that your Validator Client is using this address as its fee recipient!%s\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: You are in Native Mode; you MUST ensure that your Validator Client is using this address as its fee recipient!") } } else { - fmt.Printf("The node is not opted into the Smoothing Pool.\nTo learn more about the Smoothing Pool, please visit %s.\n", smoothingPoolLink) + fmt.Println("The node is not opted into the Smoothing Pool.") + fmt.Printf("To learn more about the Smoothing Pool, please visit %s.\n", smoothingPoolLink) // Count the number of 8 ETH, <10% commission minipools poolsWithMissingCommission := 0 leb16wei := new(big.Int) @@ -311,12 +296,8 @@ func getStatus(c *cli.Context) error { poolsWithMissingCommission++ } } - if poolsWithMissingCommission == 1 { - fmt.Printf("%sYou have %d minipool that would earn extra commission if you opted into the smoothing pool!%s\n", colorYellow, poolsWithMissingCommission, colorReset) - fmt.Println("See https://rpips.rocketpool.net/RPIPs/RPIP-62 for more information about bonus commission, or run `rocketpool node join-smoothing-pool` to opt in.") - } - if poolsWithMissingCommission > 1 { - fmt.Printf("%sYou have %d minipools that would earn extra commission if you opted into the smoothing pool!%s\n", colorYellow, poolsWithMissingCommission, colorReset) + if poolsWithMissingCommission > 0 { + color.YellowPrintf("You have %d minipool(s) that would earn extra commission if you opted into the smoothing pool!\n", poolsWithMissingCommission) fmt.Println("See https://rpips.rocketpool.net/RPIPs/RPIP-62 for more information about bonus commission, or run `rocketpool node join-smoothing-pool` to opt in.") } } @@ -324,7 +305,7 @@ func getStatus(c *cli.Context) error { fmt.Println() // RPL stake details - fmt.Printf("%s=== RPL Stake ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== RPL Stake ===") fmt.Println("NOTE: The following figures take *any pending bond reductions* into account.") fmt.Println() fmt.Printf("The node has a total stake of %.6f RPL.\n", math.RoundDown(eth.WeiToEth(status.TotalRplStake), 6)) @@ -383,7 +364,7 @@ func getStatus(c *cli.Context) error { if status.MinipoolCounts.Total > 0 { // Minipool details - fmt.Printf("%s=== Minipools ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Minipools ===") // Minipools fmt.Printf("The node has a total of %d active minipool(s):\n", status.MinipoolCounts.Total-status.MinipoolCounts.Finalised) @@ -422,7 +403,8 @@ func getStatus(c *cli.Context) error { } if status.Warning != "" { - fmt.Printf("\n%sWARNING: %s%s\n", colorRed, status.Warning, colorReset) + fmt.Println() + color.RedPrintln("WARNING: " + status.Warning) } // Return diff --git a/rocketpool-cli/node/sync.go b/rocketpool-cli/node/sync.go index e4aff91a2..471ca0490 100644 --- a/rocketpool-cli/node/sync.go +++ b/rocketpool-cli/node/sync.go @@ -10,6 +10,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) // Settings @@ -71,10 +72,9 @@ func getSyncProgress(c *cli.Context) error { return err } if !depositContractInfo.SufficientSync { - colorReset := "\033[0m" - colorYellow := "\033[33m" - fmt.Printf("%sYour execution client hasn't synced enough to determine if your execution and consensus clients are on the same network.\n", colorYellow) - fmt.Printf("To run this safety check, try again later when the execution client has made more sync progress.%s\n\n", colorReset) + color.YellowPrintln("Your execution client hasn't synced enough to determine if your execution and consensus clients are on the same network.") + color.YellowPrintln("To run this safety check, try again later when the execution client has made more sync progress.") + fmt.Println() } else if depositContractInfo.RPNetwork != depositContractInfo.BeaconNetwork || depositContractInfo.RPDepositContract != depositContractInfo.BeaconDepositContract { cliutils.PrintDepositMismatchError( diff --git a/rocketpool-cli/node/withdraw-rpl.go b/rocketpool-cli/node/withdraw-rpl.go index 4546742de..71e504d49 100644 --- a/rocketpool-cli/node/withdraw-rpl.go +++ b/rocketpool-cli/node/withdraw-rpl.go @@ -13,6 +13,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/rocket-pool/smartnode/shared/utils/math" ) @@ -49,7 +50,7 @@ func nodeWithdrawRpl(c *cli.Context) error { fmt.Println() fmt.Print("1. Request to unstake a certain RPL amount;") fmt.Println() - fmt.Printf("2. Wait for the unstaking period to end (currently %s%s%s), and then withdraw the RPL.", colorYellow, unstakingDurationString, colorReset) + fmt.Printf("2. Wait for the unstaking period to end (currently %s), and then withdraw the RPL.", color.Yellow(unstakingDurationString)) fmt.Println() fmt.Println() @@ -57,7 +58,7 @@ func nodeWithdrawRpl(c *cli.Context) error { fmt.Println() fmt.Printf("Your node currently has %.6f RPL locked on pDAO proposals.", math.RoundDown(eth.WeiToEth(status.NodeRPLLocked), 6)) fmt.Println() - fmt.Printf("Your node's RPL withdrawal address is %s%s%s.\n", colorBlue, status.RPLWithdrawalAddress.String(), colorReset) + fmt.Printf("Your node's RPL withdrawal address is %s.\n", color.LightBlue(status.RPLWithdrawalAddress.String())) fmt.Println() // Check if the node has unstaking RPL and if the unstaking period passed considering the last unstake time @@ -139,8 +140,8 @@ func nodeWithdrawRpl(c *cli.Context) error { // Inform users that the unstaking period will reset if they make another unstaking request if !cooldownPassed && hasUnstakingRPL { fmt.Printf("You have %.6f RPL currently unstaking until %s (%s from now).\n", math.RoundDown(eth.WeiToEth(status.UnstakingRPL), 6), unstakingPeriodEnd.Format(TimeFormat), timeUntilUnstakingPeriodEnd.String()) - fmt.Printf("%sRequesting to unstake additional RPL will reset the unstaking period.\n%s", colorYellow, colorReset) - fmt.Printf("%sThe unstaking period is %s.\n%s", colorYellow, unstakingDurationString, colorReset) + color.YellowPrintln("Requesting to unstake additional RPL will reset the unstaking period.") + color.YellowPrintf("The unstaking period is %s.\n", unstakingDurationString) if !prompt.Confirm("Are you sure you would like to continue?") { os.Exit(0) @@ -240,7 +241,7 @@ func nodeWithdrawRpl(c *cli.Context) error { return nil } fmt.Println("Unstaking legacy RPL follows the same 2-step process as unstaking megapool staked RPL.") - fmt.Printf("Unstaked legacy RPL can be withdrawn after an unstaking period of %s%s%s.\n", colorYellow, unstakingDurationString, colorReset) + fmt.Printf("Unstaked legacy RPL can be withdrawn after an unstaking period of %s.\n", color.Yellow(unstakingDurationString)) fmt.Println() // Get the maximum withdrawable amount based on constraints diff --git a/rocketpool-cli/pdao/set-allow-list.go b/rocketpool-cli/pdao/set-allow-list.go index 923848e08..162775bec 100644 --- a/rocketpool-cli/pdao/set-allow-list.go +++ b/rocketpool-cli/pdao/set-allow-list.go @@ -8,6 +8,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) @@ -58,9 +59,9 @@ func setAllowListedControllers(c *cli.Context) error { // Prompt for confirmation if addressListStr == "" { - fmt.Printf("%sYou are proposing to remove all allowlisted controllers%s\n", colorGreen, colorReset) + color.GreenPrintln("You are proposing to remove all allowlisted controllers") } else { - fmt.Printf("%sYou have selected propose %v as the allowlisted controllers%s\n", colorGreen, addressListStr, colorReset) + color.GreenPrintln("You have selected propose %v as the allowlisted controllers", addressListStr) } fmt.Println() diff --git a/rocketpool-cli/pdao/status.go b/rocketpool-cli/pdao/status.go index b879bee17..236253343 100644 --- a/rocketpool-cli/pdao/status.go +++ b/rocketpool-cli/pdao/status.go @@ -14,13 +14,11 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/math" ) const ( - colorBlue string = "\033[36m" - colorReset string = "\033[0m" - colorGreen string = "\033[32m" signallingAddressLink string = "https://docs.rocketpool.net/pdao/participate#setting-your-snapshot-signalling-address" challengeLink string = "https://docs.rocketpool.net/pdao#challenge-process" ) @@ -74,13 +72,12 @@ func getStatus(c *cli.Context) error { claimableBonds := claimableBondsResponse.ClaimableBonds // Signalling Status - fmt.Printf("%s=== Signalling on Snapshot ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Signalling on Snapshot ===") blankAddress := common.Address{} if response.SignallingAddress == blankAddress { fmt.Printf("The node does not currently have a snapshot signalling address set.\nTo learn more about snapshot signalling, please visit %s.\n", signallingAddressLink) } else { - fmt.Printf("The node has a signalling address of %s%s%s which can represent it when voting on Rocket Pool Snapshot governance proposals.", colorBlue, response.SignallingAddressFormatted, colorReset) - fmt.Println() + fmt.Printf("The node has a signalling address of %s which can represent it when voting on Rocket Pool Snapshot governance proposals.\n", color.LightBlue(response.SignallingAddressFormatted)) } if response.SnapshotResponse.Error != "" { @@ -97,14 +94,14 @@ func getStatus(c *cli.Context) error { } // Onchain Voting Status - fmt.Printf("%s=== Onchain Voting ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Onchain Voting ===") if response.OnchainVotingDelegate == blankAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals after it initializes voting.") } else if response.OnchainVotingDelegate == response.AccountAddress { fmt.Println("The node doesn't have a delegate, which means it can vote directly on onchain proposals. You can have another node represent you by running `rocketpool p svd
`.") } else { - fmt.Printf("The node has a voting delegate of %s%s%s which can represent it when voting on Rocket Pool onchain governance proposals.\n", colorBlue, response.OnchainVotingDelegateFormatted, colorReset) + fmt.Printf("The node has a voting delegate of %s which can represent it when voting on Rocket Pool onchain governance proposals.\n", color.LightBlue(response.OnchainVotingDelegateFormatted)) } fmt.Printf("The node's local voting power: %.10f\n", eth.WeiToEth(response.VotingPower)) @@ -118,7 +115,7 @@ func getStatus(c *cli.Context) error { fmt.Println("") // Claimable Bonds Status: - fmt.Printf("%s=== Claimable RPL Bonds ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Claimable RPL Bonds ===") if response.IsRPLLockingAllowed { fmt.Print("The node is allowed to lock RPL to create governance proposals/challenges.\n") if response.NodeRPLLocked.Cmp(big.NewInt(0)) != 0 { @@ -137,7 +134,7 @@ func getStatus(c *cli.Context) error { fmt.Println("") // Check if PDAO proposal checking duty is enabled - fmt.Printf("%s=== PDAO Proposal Checking Duty ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== PDAO Proposal Checking Duty ===") // Make sure the user opted into this duty if response.VerifyEnabled { fmt.Println("The node has PDAO proposal checking duties enabled. It will periodically check for proposals to challenge.") @@ -148,7 +145,7 @@ func getStatus(c *cli.Context) error { fmt.Println("") // Claimable Bonds Status: - fmt.Printf("%s=== Pending, Active and Succeeded Proposals ===%s\n", colorGreen, colorReset) + color.GreenPrintln("=== Pending, Active and Succeeded Proposals ===") // Get proposals by state stateProposals := map[string][]api.PDAOProposalWithNodeVoteDirection{} for _, proposal := range allProposals.Proposals { @@ -180,7 +177,8 @@ func getStatus(c *cli.Context) error { // Print message for Succeeded Proposals if stateName == "Succeeded" { succeededExists = true - fmt.Printf("%sThe following proposal(s) have succeeded and are waiting to be executed. Use `rocketpool pdao proposals execute` to execute.%s\n\n", colorBlue, colorReset) + color.LightBluePrintln("The following proposal(s) have succeeded and are waiting to be executed. Use `rocketpool pdao proposals execute` to execute.") + fmt.Println() } // Proposal state count diff --git a/rocketpool-cli/rocketpool-cli.go b/rocketpool-cli/rocketpool-cli.go index 2599fb2fd..c55005581 100644 --- a/rocketpool-cli/rocketpool-cli.go +++ b/rocketpool-cli/rocketpool-cli.go @@ -23,12 +23,11 @@ import ( "github.com/rocket-pool/smartnode/shared" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) const ( - colorReset string = "\033[0m" - colorYellow string = "\033[33m" - maxAlertItems int = 3 + maxAlertItems int = 3 ) // Run @@ -181,7 +180,7 @@ A special thanks to the Rocket Pool community for all their contributions. } if len(response.Alerts) > 0 { - fmt.Printf("\n%s=== Alerts ===%s\n", colorYellow, colorReset) + color.YellowPrintln("=== Alerts ===") for i, alert := range response.Alerts { fmt.Println(alert.ColorString()) if i == maxAlertItems-1 { diff --git a/rocketpool-cli/service/commands.go b/rocketpool-cli/service/commands.go index 2f0567029..5db8034ec 100644 --- a/rocketpool-cli/service/commands.go +++ b/rocketpool-cli/service/commands.go @@ -11,6 +11,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/config" cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) // Creates CLI argument flags from the parameters of the configuration struct @@ -440,7 +441,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { { Name: "resync-eth1", - Usage: fmt.Sprintf("%sDeletes the main ETH1 client's chain data and resyncs it from scratch. Only use this as a last resort!%s", colorRed, colorReset), + Usage: color.Red("Deletes the main ETH1 client's chain data and resyncs it from scratch. Only use this as a last resort!"), UsageText: "rocketpool service resync-eth1", Action: func(c *cli.Context) error { @@ -457,7 +458,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { { Name: "resync-eth2", - Usage: fmt.Sprintf("%sDeletes the ETH2 client's chain data and resyncs it from scratch. Only use this as a last resort!%s", colorRed, colorReset), + Usage: color.Red("Deletes the ETH2 client's chain data and resyncs it from scratch. Only use this as a last resort!"), UsageText: "rocketpool service resync-eth2", Action: func(c *cli.Context) error { @@ -475,7 +476,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { { Name: "terminate", Aliases: []string{"t"}, - Usage: fmt.Sprintf("%sDeletes all of the Rocket Pool Docker containers and volumes, including your ETH1 and ETH2 chain data and your Prometheus database (if metrics are enabled). Also removes your entire `.rocketpool` configuration folder, including your wallet, password, and validator keys. Only use this if you are cleaning up the Smart Node and want to start over!%s", colorRed, colorReset), + Usage: color.Red("Deletes all of the Rocket Pool Docker containers and volumes, including your ETH1 and ETH2 chain data and your Prometheus database (if metrics are enabled). Also removes your entire `.rocketpool` configuration folder, including your wallet, password, and validator keys. Only use this if you are cleaning up the Smart Node and want to start over!"), UsageText: "rocketpool service terminate [options]", Flags: []cli.Flag{ cli.BoolFlag{ diff --git a/rocketpool-cli/service/service.go b/rocketpool-cli/service/service.go index 42d77abcc..7876c0519 100644 --- a/rocketpool-cli/service/service.go +++ b/rocketpool-cli/service/service.go @@ -20,6 +20,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/shirou/gopsutil/v3/disk" ) @@ -44,13 +45,7 @@ const ( // Just ignore the version tag. dockerImageRegex string = "(?P.+):.*" - colorReset string = "\033[0m" - colorBold string = "\033[1m" - colorRed string = "\033[31m" - colorYellow string = "\033[33m" - colorGreen string = "\033[32m" - colorLightBlue string = "\033[36m" - clearLine string = "\033[2K" + clearLine string = "\033[2K" ) // Install the Rocket Pool service @@ -59,8 +54,10 @@ func installService(c *cli.Context) error { // Prompt for confirmation if !(c.Bool("yes") || prompt.Confirm( - "The Rocket Pool %s service will be installed.\n\n%sIf you're upgrading, your existing configuration will be backed up and preserved.\nAll of your previous settings will be migrated automatically.%s\nAre you sure you want to continue?", - shared.RocketPoolVersion(), colorGreen, colorReset, + "%s", + fmt.Sprintf("The Rocket Pool %s service will be installed.\n\n", shared.RocketPoolVersion())+ + color.Green("If you're upgrading, your existing configuration will be backed up and preserved.\nAll of your previous settings will be migrated automatically.\n")+ + "Are you sure you want to continue?", )) { fmt.Println("Cancelled.") return nil @@ -102,13 +99,15 @@ func installService(c *cli.Context) error { } // Report next steps - fmt.Printf("%s\n=== Next Steps ===\n", colorLightBlue) - fmt.Printf("Run 'rocketpool service config' to review the settings changes for this update, or to continue setting up your node.%s\n", colorReset) + color.LightBluePrintln("=== Next Steps ===") + color.LightBluePrintln("Run 'rocketpool service config' to review the settings changes for this update, or to continue setting up your node.") // Print the docker permissions notice if isNew { - fmt.Printf("\n%sNOTE:\nSince this is your first time installing Rocket Pool, please start a new shell session by logging out and back in or restarting the machine.\n", colorYellow) - fmt.Printf("This is necessary for your user account to have permissions to use Docker.%s", colorReset) + fmt.Println() + color.YellowPrintln("NOTE:") + color.YellowPrintln("Since this is your first time installing Rocket Pool, please start a new shell session by logging out and back in or restarting the machine.") + color.YellowPrintln("This is necessary for your user account to have permissions to use Docker.") } return nil @@ -121,9 +120,9 @@ func printPatchNotes(c *cli.Context) { fmt.Print(shared.Logo()) fmt.Println() fmt.Println() - fmt.Printf("%s=== Smart Node v%s ===%s\n", colorGreen, shared.RocketPoolVersion(), colorReset) + color.GreenPrintf("=== Smart Node v%s ===\n", shared.RocketPoolVersion()) fmt.Println() - fmt.Printf("Changes you should be aware of before starting:\n") + fmt.Println("Changes you should be aware of before starting:") fmt.Println() fmt.Println("This Smart Node version is compatible with the Saturn 1 upgrade. The upgrade took place on Feb 18, 2026 00:00:00 UTC.") fmt.Println("For more information about the biggest Rocket Pool upgrade ever, please see the official documentation: https://docs.rocketpool.net/upgrades/saturn-1/whats-new") @@ -155,7 +154,8 @@ func installUpdateTracker(c *cli.Context) error { fmt.Println("") fmt.Println("The Rocket Pool update tracker service was successfully installed!") fmt.Println("") - fmt.Printf("%sNOTE:\nPlease restart the Smart Node stack to enable update tracking on the metrics dashboard.%s\n", colorYellow, colorReset) + color.YellowPrintln("NOTE:") + color.YellowPrintln("Please restart the Smart Node stack to enable update tracking on the metrics dashboard.") fmt.Println("") return nil @@ -196,7 +196,8 @@ func configureService(c *cli.Context) error { } _, err = os.Stat(path) if os.IsNotExist(err) { - fmt.Printf("%sYour configured Rocket Pool directory of [%s] does not exist.\nPlease follow the instructions at https://docs.rocketpool.net/node-staking/docker to install the Smart Node.%s\n", colorYellow, path, colorReset) + color.YellowPrintf("Your configured Rocket Pool directory of [%s] does not exist.\n", path) + color.YellowPrintln("Please follow the instructions at https://docs.rocketpool.net/node-staking/docker to install the Smart Node.") return nil } @@ -273,7 +274,12 @@ func configureService(c *cli.Context) error { return fmt.Errorf("error saving config: %w", err) } - fmt.Printf("%sWARNING: You have requested to change networks.\n\nAll of your existing chain data, your node wallet, and your validator keys will be removed. If you had a Checkpoint Sync URL provided for your Consensus client, it will be removed and you will need to specify a different one that supports the new network.\n\nPlease confirm you have backed up everything you want to keep, because it will be deleted if you answer `y` to the prompt below.\n\n%s", colorYellow, colorReset) + color.YellowPrintln("WARNING: You have requested to change networks.") + fmt.Println() + color.YellowPrintln("All of your existing chain data, your node wallet, and your validator keys will be removed. If you had a Checkpoint Sync URL provided for your Consensus client, it will be removed and you will need to specify a different one that supports the new network.") + fmt.Println() + color.YellowPrintln("Please confirm you have backed up everything you want to keep, because it will be deleted if you answer `y` to the prompt below.") + fmt.Println() if !prompt.Confirm("Would you like the Smart Node to automatically switch networks for you? This will destroy and rebuild your `data` folder and all of Rocket Pool's Docker containers.") { fmt.Println("To change networks manually, please follow the steps laid out in the Node Operator's guide (https://docs.rocketpool.net/node-staking/config-docker#choosing-a-network).") @@ -282,7 +288,8 @@ func configureService(c *cli.Context) error { err = changeNetworks(c, rp, fmt.Sprintf("%s%s", prefix, ApiContainerSuffix)) if err != nil { - fmt.Printf("%s%s%s\nThe Smart Node could not automatically change networks for you, so you will have to run the steps manually. Please follow the steps laid out in the Node Operator's guide (https://docs.rocketpool.net/node-staking/mainnet.html).\n", colorRed, err.Error(), colorReset) + color.RedPrintln(err.Error()) + fmt.Println("The Smart Node could not automatically change networks for you, so you will have to run the steps manually. Please follow the steps laid out in the Node Operator's guide (https://docs.rocketpool.net/node-staking/mainnet.html).") } return nil } @@ -487,9 +494,11 @@ func startService(c *cli.Context, ignoreConfigSuggestion bool) error { // Force all Docker or all Hybrid if cfg.ExecutionClientMode.Value.(cfgtypes.Mode) == cfgtypes.Mode_Local && cfg.ConsensusClientMode.Value.(cfgtypes.Mode) == cfgtypes.Mode_External { - fmt.Printf("%sYou are using a locally-managed Execution client and an externally-managed Consensus client.\nThis configuration is not compatible with The Merge; please select either locally-managed or externally-managed for both the EC and CC.%s\n", colorRed, colorReset) + color.RedPrintln("You are using a locally-managed Execution client and an externally-managed Consensus client.") + color.RedPrintln("This configuration is not compatible with The Merge; please select either locally-managed or externally-managed for both the EC and CC.") } else if cfg.ExecutionClientMode.Value.(cfgtypes.Mode) == cfgtypes.Mode_External && cfg.ConsensusClientMode.Value.(cfgtypes.Mode) == cfgtypes.Mode_Local { - fmt.Printf("%sYou are using an externally-managed Execution client and a locally-managed Consensus client.\nThis configuration is not compatible with The Merge; please select either locally-managed or externally-managed for both the EC and CC.%s\n", colorRed, colorReset) + color.RedPrintln("You are using an externally-managed Execution client and a locally-managed Consensus client.") + color.RedPrintln("This configuration is not compatible with The Merge; please select either locally-managed or externally-managed for both the EC and CC.") } if isNew { @@ -508,7 +517,7 @@ func startService(c *cli.Context, ignoreConfigSuggestion bool) error { return fmt.Errorf("error upgrading configuration with the latest parameters: %w", err) } rp.SaveConfig(cfg) - fmt.Printf("%sUpdated settings successfully.%s\n", colorGreen, colorReset) + color.GreenPrintln("Updated settings successfully.") } else { fmt.Println("Cancelled.") return nil @@ -536,11 +545,12 @@ func startService(c *cli.Context, ignoreConfigSuggestion bool) error { // Validate the config errors := cfg.Validate() if len(errors) > 0 { - fmt.Printf("%sYour configuration encountered errors. You must correct the following in order to start Rocket Pool:\n\n", colorRed) + color.RedPrintln("Your configuration encountered errors. You must correct the following in order to start Rocket Pool:") + fmt.Println() for _, err := range errors { - fmt.Printf("%s\n\n", err) + color.RedPrintf("%s\n", err) + fmt.Println() } - fmt.Println(colorReset) return nil } @@ -548,29 +558,35 @@ func startService(c *cli.Context, ignoreConfigSuggestion bool) error { // Do the client swap check err := checkForValidatorChange(rp, cfg) if err != nil { - fmt.Printf("%sWARNING: couldn't verify that the validator container can be safely restarted:\n\t%s\n", colorYellow, err.Error()) - fmt.Println("If you are changing to a different ETH2 client, it may resubmit an attestation you have already submitted.") - fmt.Println("This will slash your validator!") - fmt.Println("To prevent slashing, you must wait 15 minutes from the time you stopped the clients before starting them again.") + color.YellowPrintln("WARNING: couldn't verify that the validator container can be safely restarted:") + color.YellowPrintf("\t%s\n", err.Error()) + color.YellowPrintln("If you are changing to a different ETH2 client, it may resubmit an attestation you have already submitted.") + color.YellowPrintln("This will slash your validator!") + color.YellowPrintln("To prevent slashing, you must wait 15 minutes from the time you stopped the clients before starting them again.") fmt.Println() - fmt.Println("**If you did NOT change clients, you can safely ignore this warning.**") + color.YellowPrintln("**If you did NOT change clients, you can safely ignore this warning.**") fmt.Println() - if !prompt.Confirm("Press y when you understand the above warning, have waited, and are ready to start Rocket Pool:%s", colorReset) { + if !prompt.ConfirmYellow("Press y when you understand the above warning, have waited, and are ready to start Rocket Pool:") { fmt.Println("Cancelled.") return nil } } } else { - fmt.Printf("%sIgnoring anti-slashing safety delay.%s\n", colorYellow, colorReset) + color.YellowPrintln("Ignoring anti-slashing safety delay.") } // Write a note on doppelganger protection doppelgangerEnabled, err := cfg.IsDoppelgangerEnabled() if err != nil { - fmt.Printf("%sCouldn't check if you have Doppelganger Protection enabled: %s\nIf you do, your validator will miss up to 3 attestations when it starts.\nThis is *intentional* and does not indicate a problem with your node.%s\n\n", colorYellow, err.Error(), colorReset) + color.YellowPrintf("Couldn't check if you have Doppelganger Protection enabled: %s\n", err.Error()) + color.YellowPrintln("If you do, your validator will miss up to 3 attestations when it starts.") + color.YellowPrintln("This is *intentional* and does not indicate a problem with your node.") } else if doppelgangerEnabled { - fmt.Printf("%sNOTE: You currently have Doppelganger Protection enabled.\nYour validator will miss up to 3 attestations when it starts.\nThis is *intentional* and does not indicate a problem with your node.%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: You currently have Doppelganger Protection enabled.") + color.YellowPrintln("Your validator will miss up to 3 attestations when it starts.") + color.YellowPrintln("This is *intentional* and does not indicate a problem with your node.") } + fmt.Println() // Start service err = rp.StartService(getComposeFiles(c)) @@ -622,7 +638,10 @@ func checkForValidatorChange(rp *rocketpool.Client, cfg *config.RocketPoolConfig consensusClient, _ := cfg.GetSelectedConsensusClient() // Warn about Lodestar if consensusClient == cfgtypes.ConsensusClient_Lodestar { - fmt.Printf("%sNOTE:\nIf this is your first time running Lodestar and you have existing minipools, you must run `rocketpool wallet rebuild` after the Smart Node starts to generate the validator keys for it.\nIf you have run it before or you don't have any minipools, you can ignore this message.%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE:") + color.YellowPrintln("If this is your first time running Lodestar and you have existing minipools, you must run `rocketpool wallet rebuild` after the Smart Node starts to generate the validator keys for it.") + color.YellowPrintln("If you have run it before or you don't have any minipools, you can ignore this message.") + fmt.Println() } // Get the time that the container responsible for validator duties exited @@ -642,7 +661,7 @@ func checkForValidatorChange(rp *rocketpool.Client, cfg *config.RocketPoolConfig return fmt.Errorf("Error getting container [%s] status: %w", validatorDutyContainerName, err) } if validatorFinishTime == zeroTime || status == "running" { - fmt.Printf("%sValidator is currently running, stopping it...%s\n", colorYellow, colorReset) + color.YellowPrintln("Validator is currently running, stopping it...") response, err := rp.StopContainer(validatorDutyContainerName) validatorFinishTime = time.Now() if err != nil { @@ -660,12 +679,13 @@ func checkForValidatorChange(rp *rocketpool.Client, cfg *config.RocketPoolConfig fmt.Printf("The validator has been offline for %s, which is long enough to prevent slashing.\n", time.Since(validatorFinishTime)) fmt.Println("The new client can be safely started.") } else { - fmt.Printf("%s=== WARNING ===\n", colorRed) - fmt.Printf("You have changed your validator client from %s to %s. Only %s has elapsed since you stopped %s.\n", currentValidatorName, pendingValidatorName, time.Since(validatorFinishTime), currentValidatorName) - fmt.Printf("If you were actively validating while using %s, starting %s without waiting will cause your validators to be slashed due to duplicate attestations!", currentValidatorName, pendingValidatorName) - fmt.Println("To prevent slashing, Rocket Pool will delay activating the new client for 15 minutes.") - fmt.Println("See the documentation for a more detailed explanation: https://docs.rocketpool.net/node-staking/maintenance/node-migration.html#slashing-and-the-slashing-database") - fmt.Printf("If you have read the documentation, understand the risks, and want to bypass this cooldown, run `rocketpool service start --ignore-slash-timer`.%s\n\n", colorReset) + color.RedPrintln("=== WARNING ===") + color.RedPrintf("You have changed your validator client from %s to %s. Only %s has elapsed since you stopped %s.\n", currentValidatorName, pendingValidatorName, time.Since(validatorFinishTime), currentValidatorName) + color.RedPrintf("If you were actively validating while using %s, starting %s without waiting will cause your validators to be slashed due to duplicate attestations!", currentValidatorName, pendingValidatorName) + color.RedPrintln("To prevent slashing, Rocket Pool will delay activating the new client for 15 minutes.") + color.RedPrintln("See the documentation for a more detailed explanation: https://docs.rocketpool.net/node-staking/maintenance/node-migration.html#slashing-and-the-slashing-database") + color.RedPrintln("If you have read the documentation, understand the risks, and want to bypass this cooldown, run `rocketpool service start --ignore-slash-timer`.") + fmt.Println() // Wait for 15 minutes for remainingTime > 0 { @@ -675,7 +695,6 @@ func checkForValidatorChange(rp *rocketpool.Client, cfg *config.RocketPoolConfig fmt.Printf("%s\r", clearLine) } - fmt.Println(colorReset) fmt.Println("You may now safely start the validator without fear of being slashed.") } } @@ -770,10 +789,12 @@ func pruneExecutionClient(c *cli.Context) error { // Print the appropriate warnings before pruning if selectedEc == cfgtypes.ExecutionClient_Geth { - fmt.Printf("%sGeth has a new feature that renders pruning obsolete. However, as this is a new feature you may have to resync with `rocketpool service resync-eth1` before this takes effect.%s\n", colorYellow, colorReset) + color.YellowPrintln("Geth has a new feature that renders pruning obsolete. However, as this is a new feature you may have to resync with `rocketpool service resync-eth1` before this takes effect.") fmt.Println("This will shut down your main execution client and prune its database, freeing up disk space.") if cfg.UseFallbackClients.Value == false { - fmt.Printf("%sYou do not have a fallback execution client configured.\nYour node will no longer be able to perform any validation duties (attesting or proposing blocks) until pruning is done.\nPlease configure a fallback client with `rocketpool service config` before running this.%s\n", colorRed, colorReset) + color.RedPrintln("You do not have a fallback execution client configured.") + color.RedPrintln("Your node will no longer be able to perform any validation duties (attesting or proposing blocks) until pruning is done.") + color.RedPrintln("Please configure a fallback client with `rocketpool service config` before running this.") } else { fmt.Println("You have fallback clients enabled. Rocket Pool (and your consensus client) will use that while the main client is pruning.") } @@ -827,7 +848,7 @@ func pruneExecutionClient(c *cli.Context) error { pruneFreeSpaceRequired = NethermindPruneFreeSpaceRequired } if diskUsage.Free < pruneFreeSpaceRequired { - return fmt.Errorf("%sYour disk must have %s GiB free to prune, but it only has %s free. Please free some space before pruning.%s", colorRed, humanize.IBytes(pruneFreeSpaceRequired), freeSpaceHuman, colorReset) + return fmt.Errorf("Your disk must have %s GiB free to prune, but it only has %s free. Please free some space before pruning.", humanize.IBytes(pruneFreeSpaceRequired), freeSpaceHuman) } fmt.Printf("Your disk has %s free, which is enough to prune.\n", freeSpaceHuman) @@ -876,7 +897,8 @@ func pruneExecutionClient(c *cli.Context) error { fmt.Println("Done! Your main execution client is now pruning. You can follow its progress with `rocketpool service logs eth1`.") fmt.Println("Once it's done, it will restart automatically and resume normal operation.") - fmt.Println(colorYellow + "NOTE: While pruning, you **cannot** interrupt the client (e.g. by restarting) or you risk corrupting the database!\nYou must let it run to completion!" + colorReset) + color.YellowPrintln("NOTE: While pruning, you **cannot** interrupt the client (e.g. by restarting) or you risk corrupting the database!") + color.YellowPrintln("You must let it run to completion!") return nil @@ -985,10 +1007,15 @@ func pauseService(c *cli.Context) (bool, error) { // Write a note on doppelganger protection doppelgangerEnabled, err := cfg.IsDoppelgangerEnabled() if err != nil { - fmt.Printf("%sCouldn't check if you have Doppelganger Protection enabled: %s\nIf you do, stopping your validator will cause it to miss up to 3 attestations when it next starts.\nThis is *intentional* and does not indicate a problem with your node.%s\n\n", colorYellow, err.Error(), colorReset) + color.YellowPrintf("Couldn't check if you have Doppelganger Protection enabled: %s\n", err.Error()) + color.YellowPrintln("If you do, stopping your validator will cause it to miss up to 3 attestations when it next starts.") + color.YellowPrintln("This is *intentional* and does not indicate a problem with your node.") } else if doppelgangerEnabled { - fmt.Printf("%sNOTE: You currently have Doppelganger Protection enabled.\nIf you stop your validator, it will miss up to 3 attestations when it next starts.\nThis is *intentional* and does not indicate a problem with your node.%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: You currently have Doppelganger Protection enabled.") + color.YellowPrintln("If you stop your validator, it will miss up to 3 attestations when it next starts.") + color.YellowPrintln("This is *intentional* and does not indicate a problem with your node.") } + fmt.Println() // Prompt for confirmation if !(c.Bool("yes") || prompt.Confirm("Are you sure you want to pause the Rocket Pool service? Any staking minipools and megapool validators will be penalized!")) { @@ -1006,7 +1033,7 @@ func pauseService(c *cli.Context) (bool, error) { func terminateService(c *cli.Context) error { // Prompt for confirmation - if !(c.Bool("yes") || prompt.Confirm("%sWARNING: Are you sure you want to terminate the Rocket Pool service? Any staking minipools will be penalized, your ETH1 and ETH2 chain databases will be deleted, you will lose ALL of your sync progress, and you will lose your Prometheus metrics database!\nAfter doing this, you will have to **reinstall** the Smart Node uses `rocketpool service install -d` in order to use it again.%s", colorRed, colorReset)) { + if !(c.Bool("yes") || prompt.ConfirmRed("WARNING: Are you sure you want to terminate the Rocket Pool service? Any staking minipools will be penalized, your ETH1 and ETH2 chain databases will be deleted, you will lose ALL of your sync progress, and you will lose your Prometheus metrics database!\nAfter doing this, you will have to **reinstall** the Smart Node uses `rocketpool service install -d` in order to use it again.")) { fmt.Println("Cancelled.") return nil } @@ -1218,7 +1245,8 @@ func resyncEth1(c *cli.Context) error { } fmt.Println("This will delete the chain data of your primary ETH1 client and resync it from scratch.") - fmt.Printf("%sYou should only do this if your ETH1 client has failed and can no longer start or sync properly.\nThis is meant to be a last resort.%s\n", colorYellow, colorReset) + color.YellowPrintln("You should only do this if your ETH1 client has failed and can no longer start or sync properly.") + color.YellowPrintln("This is meant to be a last resort.") // Get the container prefix prefix, err := rp.GetContainerPrefix() @@ -1227,7 +1255,7 @@ func resyncEth1(c *cli.Context) error { } // Prompt for confirmation - if !(c.Bool("yes") || prompt.Confirm("%sAre you SURE you want to delete and resync your main ETH1 client from scratch? This cannot be undone!%s", colorRed, colorReset)) { + if !(c.Bool("yes") || prompt.ConfirmRed("Are you SURE you want to delete and resync your main ETH1 client from scratch? This cannot be undone!")) { fmt.Println("Cancelled.") return nil } @@ -1237,10 +1265,10 @@ func resyncEth1(c *cli.Context) error { fmt.Printf("Stopping %s...\n", executionContainerName) result, err := rp.StopContainer(executionContainerName) if err != nil { - fmt.Printf("%sWARNING: Stopping main ETH1 container failed: %s%s\n", colorYellow, err.Error(), colorReset) + color.YellowPrintf("WARNING: Stopping main ETH1 container failed: %s\n", err.Error()) } if result != executionContainerName { - fmt.Printf("%sWARNING: Unexpected output while stopping main ETH1 container: %s%s\n", colorYellow, result, colorReset) + color.YellowPrintf("WARNING: Unexpected output while stopping main ETH1 container: %s\n", result) } // Get ETH1 volume name @@ -1299,7 +1327,9 @@ func resyncEth2(c *cli.Context) error { } fmt.Println("This will delete the chain data of your ETH2 client and resync it from scratch.") - fmt.Printf("%sYou should only do this if your ETH2 client has failed and can no longer start or sync properly.\nThis is meant to be a last resort.%s\n\n", colorYellow, colorReset) + color.YellowPrintln("You should only do this if your ETH2 client has failed and can no longer start or sync properly.") + color.YellowPrintln("This is meant to be a last resort.") + fmt.Println() // Get the parameters that the selected client doesn't support var unsupportedParams []string @@ -1330,19 +1360,25 @@ func resyncEth2(c *cli.Context) error { } } if !supportsCheckpointSync { - fmt.Printf("%sYour ETH2 client (%s) does not support checkpoint sync.\nIf you have active validators, they %swill be considered offline and will leak ETH%s%s while the client is syncing.%s\n\n", colorRed, clientName, colorBold, colorReset, colorRed, colorReset) + color.RedPrintln("Your ETH2 client (%s) does not support checkpoint sync.", clientName) + color.RedPrintln("If you have active validators, they", color.Bold("will be considered offline and will leak ETH"), "while the client is syncing.") + fmt.Println() } else { // Get the current checkpoint sync URL checkpointSyncUrl := cfg.ConsensusCommon.CheckpointSyncProvider.Value.(string) if checkpointSyncUrl == "" { - fmt.Printf("%sYou do not have a checkpoint sync provider configured.\nIf you have active validators, they %swill be considered offline and will lose ETH%s%s until your ETH2 client finishes syncing.\nWe strongly recommend you configure a checkpoint sync provider with `rocketpool service config` so it syncs instantly before running this.%s\n\n", colorRed, colorBold, colorReset, colorRed, colorReset) + color.RedPrintln("You do not have a checkpoint sync provider configured.") + color.RedPrintln("If you have active validators, they", color.Bold("will be considered offline and will leak ETH"), "while the client is syncing.") + color.RedPrintln("We strongly recommend you configure a checkpoint sync provider with `rocketpool service config` so it syncs instantly before running this.") + fmt.Println() } else { - fmt.Printf("You have a checkpoint sync provider configured (%s).\nYour ETH2 client will use it to sync to the head of the Beacon Chain instantly after being rebuilt.\n\n", checkpointSyncUrl) + fmt.Printf("You have a checkpoint sync provider configured (%s).\n", checkpointSyncUrl) + fmt.Println("Your ETH2 client will use it to sync to the head of the Beacon Chain instantly after being rebuilt.") } } // Prompt for confirmation - if !(c.Bool("yes") || prompt.Confirm("%sAre you SURE you want to delete and resync your ETH2 client from scratch? This cannot be undone!%s", colorRed, colorReset)) { + if !(c.Bool("yes") || prompt.ConfirmRed("Are you SURE you want to delete and resync your ETH2 client from scratch? This cannot be undone!")) { fmt.Println("Cancelled.") return nil } @@ -1380,20 +1416,20 @@ func resyncEth2(c *cli.Context) error { fmt.Printf("Stopping %s...\n", container.Names) result, err := rp.StopContainer(container.Names) if err != nil { - fmt.Printf("%sWARNING: Stopping container %s failed: %s%s\n", colorYellow, container.Names, err.Error(), colorReset) + color.YellowPrintf("WARNING: Stopping container %s failed: %s\n", container.Names, err.Error()) } if result != container.Names { - fmt.Printf("%sWARNING: Unexpected output while stopping container %s: %s%s\n", colorYellow, container.Names, result, colorReset) + color.YellowPrintf("WARNING: Unexpected output while stopping container %s: %s\n", container.Names, result) } } fmt.Printf("Deleting %s...\n", container.Names) result, err := rp.RemoveContainer(container.Names) if err != nil { - fmt.Printf("%sWARNING: Deleting container %s failed: %s%s\n", colorYellow, container.Names, err.Error(), colorReset) + color.YellowPrintf("WARNING: Deleting container %s failed: %s\n", container.Names, err.Error()) } if result != container.Names { - fmt.Printf("%sWARNING: Unexpected output while deleting container %s: %s%s\n", colorYellow, container.Names, result, colorReset) + color.YellowPrintf("WARNING: Unexpected output while deleting container %s: %s\n", container.Names, result) } } diff --git a/rocketpool-cli/update/update.go b/rocketpool-cli/update/update.go index 976b3d71d..0924e5596 100644 --- a/rocketpool-cli/update/update.go +++ b/rocketpool-cli/update/update.go @@ -12,7 +12,7 @@ import ( "github.com/rocket-pool/smartnode/rocketpool-cli/update/assets" "github.com/rocket-pool/smartnode/shared" "github.com/rocket-pool/smartnode/shared/services/rocketpool" - cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) @@ -43,7 +43,7 @@ func validateOsArch() error { func errorPartialSuccess(err error) { fmt.Fprintln(os.Stderr, "An error occurred after the cli binary was updated, but before the service was.") fmt.Fprintln(os.Stderr, "The error was:") - fmt.Fprintf(os.Stderr, " %s\n", cliutils.Red(err.Error())) + color.RedPrintf(" %s\n", err.Error()) fmt.Fprintln(os.Stderr) printPartialSuccessNextSteps() os.Exit(1) @@ -97,11 +97,11 @@ func Update(c *cli.Context) error { return err } - fmt.Printf("Your detected os/architecture is %s/%s.\n", cliutils.Green(runtime.GOOS), cliutils.Green(runtime.GOARCH)) + fmt.Printf("Your detected os/architecture is %s/%s.\n", color.Green(runtime.GOOS), color.Green(runtime.GOARCH)) fmt.Println() if !c.Bool("yes") { - ok := prompt.Confirm("The cli at %s will be replaced. Continue?", cliutils.Yellow(oldBinaryPath)) + ok := prompt.Confirm("The cli at %s will be replaced. Continue?", color.Yellow(oldBinaryPath)) if !ok { return nil } @@ -124,7 +124,7 @@ func Update(c *cli.Context) error { // Download the new binary downloadUrl := fmt.Sprintf(downloadUrlFormatString, runtime.GOOS, runtime.GOARCH) - fmt.Printf("Downloading the new binary from %s\n", cliutils.Green(downloadUrl)) + fmt.Printf("Downloading the new binary from %s\n", color.Green(downloadUrl)) fmt.Println() response, err := http.Get(downloadUrl) if err != nil { @@ -152,9 +152,9 @@ func Update(c *cli.Context) error { if err != nil { return fmt.Errorf("error verifying signed binary: %w", err) } - fmt.Printf("Signed by %s\n", cliutils.Green(signer.PrimaryKey.KeyIdString())) + fmt.Printf("Signed by %s\n", color.Green(signer.PrimaryKey.KeyIdString())) for _, identity := range signer.Identities { - fmt.Printf(" %s\n", cliutils.Green(identity.Name)) + fmt.Printf(" %s\n", color.Green(identity.Name)) } } else { _, err = io.Copy(tempFile, response.Body) @@ -175,11 +175,11 @@ func Update(c *cli.Context) error { newVersion = strings.TrimPrefix(newVersion, "rocketpool version ") if strings.EqualFold(shared.RocketPoolVersion(), newVersion) && !c.Bool("force") { - fmt.Println(cliutils.Green(fmt.Sprintf("You are already on the latest version of smartnode: %s.", newVersion))) + color.GreenPrintf("You are already on the latest version of smartnode: %s.", newVersion) return nil } - fmt.Printf("Updating from %s to %s\n", cliutils.Yellow(shared.RocketPoolVersion()), cliutils.Green(newVersion)) + fmt.Printf("Updating from %s to %s\n", color.Yellow(shared.RocketPoolVersion()), color.Green(newVersion)) // Rename the temporary file to the actual binary err = os.Rename(tempFile.Name(), oldBinaryPath) @@ -188,7 +188,7 @@ func Update(c *cli.Context) error { } fmt.Println() - fmt.Println(cliutils.Green("The cli has been updated.")) + color.GreenPrintln("The cli has been updated.") fmt.Println() if !c.Bool("yes") { @@ -229,9 +229,9 @@ func Update(c *cli.Context) error { return nil } - fmt.Println(cliutils.Green(fmt.Sprintf("The upgrade to Smart Node %s has been completed.", newVersion))) + color.GreenPrintf("The upgrade to Smart Node %s has been completed.", newVersion) fmt.Println() - fmt.Println(cliutils.Yellow("Please monitor your validators for a few minutes for issues.")) + color.YellowPrintln("Please monitor your validators for a few minutes for issues.") fmt.Println() return nil diff --git a/rocketpool-cli/wallet/commands.go b/rocketpool-cli/wallet/commands.go index bad20345e..6ce635c6d 100644 --- a/rocketpool-cli/wallet/commands.go +++ b/rocketpool-cli/wallet/commands.go @@ -1,11 +1,10 @@ package wallet import ( - "fmt" - "github.com/urfave/cli" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) // Register commands @@ -233,7 +232,7 @@ func RegisterCommands(app *cli.App, name string, aliases []string) { { Name: "purge", - Usage: fmt.Sprintf("%sDeletes your node wallet, your validator keys, and restarts your Validator Client while preserving your chain data. WARNING: Only use this if you want to stop validating with this machine!%s", colorRed, colorReset), + Usage: color.Red("Deletes your node wallet, your validator keys, and restarts your Validator Client while preserving your chain data. WARNING: Only use this if you want to stop validating with this machine!"), UsageText: "rocketpool wallet purge", Action: func(c *cli.Context) error { diff --git a/rocketpool-cli/wallet/end-masquerade.go b/rocketpool-cli/wallet/end-masquerade.go index 08df9dd7c..fb9615963 100644 --- a/rocketpool-cli/wallet/end-masquerade.go +++ b/rocketpool-cli/wallet/end-masquerade.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/rocket-pool/smartnode/shared/services/rocketpool" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) @@ -28,10 +29,12 @@ func endMasquerade(c *cli.Context) error { walletUninitialized := common.Address{} if status.NodeAddress == walletUninitialized { - fmt.Printf("The node wallet is uninitialized. You will no longer be masquerading as %s%s%s.\n\n", colorBlue, status.AccountAddress.Hex(), colorReset) + fmt.Printf("The node wallet is uninitialized. You will no longer be masquerading as %s.\n", color.LightBlue(status.AccountAddress.Hex())) + fmt.Println() } else { - fmt.Printf("The node wallet is %s%s%s. You will no longer be masquerading as %s%s%s.\n\n", colorBlue, status.NodeAddress, colorReset, colorBlue, status.AccountAddress.Hex(), colorReset) + fmt.Printf("The node wallet is %s. You will no longer be masquerading as %s.\n", color.LightBlue(status.NodeAddress.Hex()), color.LightBlue(status.AccountAddress.Hex())) + fmt.Println() } // Prompt for confirmation diff --git a/rocketpool-cli/wallet/ens-name.go b/rocketpool-cli/wallet/ens-name.go index 4edc132c8..f88a0cab0 100644 --- a/rocketpool-cli/wallet/ens-name.go +++ b/rocketpool-cli/wallet/ens-name.go @@ -6,6 +6,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/gas" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" promptcli "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) @@ -19,7 +20,11 @@ func setEnsName(c *cli.Context, name string) error { } defer rp.Close() - fmt.Printf("This will confirm the node's ENS name as '%s'.\n\n%sNOTE: to confirm your name, you must first register it with the ENS application at https://app.ens.domains.\nWe recommend using a hardware wallet as the base domain, and registering your node as a subdomain of it.%s\n\n", name, colorYellow, colorReset) + fmt.Printf("This will confirm the node's ENS name as '%s'.\n", name) + fmt.Println() + color.YellowPrintln("NOTE: to confirm your name, you must first register it with the ENS application at https://app.ens.domains.") + color.YellowPrintln("We recommend using a hardware wallet as the base domain, and registering your node as a subdomain of it.") + fmt.Println() // Get gas estimate estimateGasSetName, err := rp.EstimateGasSetEnsName(name) diff --git a/rocketpool-cli/wallet/masquerade.go b/rocketpool-cli/wallet/masquerade.go index b6791cb4e..efbecb573 100644 --- a/rocketpool-cli/wallet/masquerade.go +++ b/rocketpool-cli/wallet/masquerade.go @@ -5,6 +5,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) @@ -14,7 +15,7 @@ func masquerade(c *cli.Context) error { rp := rocketpool.NewClientFromCtx(c) defer rp.Close() - fmt.Printf("Masquerading allows you to set your node address to any address you want. All commands will act as though your node wallet is for that address. Since you don't have the private key for that address, you can't submit transactions or sign messages though; commands will be %sread-only%s until you end the masquerade with `rocketpool wallet end-masquerade`.\n", colorYellow, colorReset) + fmt.Println("Masquerading allows you to set your node address to any address you want. All commands will act as though your node wallet is for that address. Since you don't have the private key for that address, you can't submit transactions or sign messages though; commands will be", color.Yellow("read-only"), "until you end the masquerade with `rocketpool wallet end-masquerade`.") fmt.Println() // Get address @@ -29,7 +30,7 @@ func masquerade(c *cli.Context) error { } // Prompt for confirmation - if !(c.Bool("yes") || prompt.Confirm("Are you sure you want to masquerade as %s%s%s?", colorBlue, addressString, colorReset)) { + if !(c.Bool("yes") || prompt.Confirm("Are you sure you want to masquerade as %s?", color.LightBlue(addressString))) { fmt.Println("Cancelled.") return nil } @@ -40,7 +41,7 @@ func masquerade(c *cli.Context) error { return fmt.Errorf("error running masquerade: %w", err) } - fmt.Printf("Your node is now masquerading as address %s%s%s.\n", colorBlue, addressString, colorReset) + fmt.Printf("Your node is now masquerading as address %s.\n", color.LightBlue(addressString)) return nil } diff --git a/rocketpool-cli/wallet/purge.go b/rocketpool-cli/wallet/purge.go index bff79d8da..993f4ecb7 100644 --- a/rocketpool-cli/wallet/purge.go +++ b/rocketpool-cli/wallet/purge.go @@ -6,6 +6,7 @@ import ( "github.com/urfave/cli" "github.com/rocket-pool/smartnode/shared/services/rocketpool" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" promptcli "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -15,7 +16,12 @@ func purge(c *cli.Context) error { rp := rocketpool.NewClientFromCtx(c) defer rp.Close() - if !promptcli.Confirm("%sWARNING: This will delete your node wallet, all of your validator keys (including externally-generated ones in the 'custom-keys' folder), and restart your Docker containers.\nYou will NO LONGER be able to attest with this machine anymore until you recover your wallet or initialize a new one.\n\nYou MUST have your node wallet's mnemonic recorded before running this, or you will lose access to your node wallet and your validators forever!\n\n%sDo you want to continue?", colorRed, colorReset) { + color.RedPrintln("WARNING: This will delete your node wallet, all of your validator keys (including externally-generated ones in the 'custom-keys' folder), and restart your Docker containers.") + color.RedPrintln("You will NO LONGER be able to attest with this machine anymore until you recover your wallet or initialize a new one.") + fmt.Println() + color.RedPrintln("You MUST have your node wallet's mnemonic recorded before running this, or you will lose access to your node wallet and your validators forever!") + fmt.Println() + if !promptcli.Confirm("Do you want to continue?") { fmt.Println("Cancelled.") return nil } @@ -24,14 +30,19 @@ func purge(c *cli.Context) error { composeFiles := c.Parent().StringSlice("compose-file") err := rp.PurgeAllKeys(composeFiles) if err != nil { - return fmt.Errorf("%w\n%sTHERE WAS AN ERROR DELETING YOUR KEYS. They most likely have not been deleted. Proceed with caution.%s", err, colorRed, colorReset) + return fmt.Errorf("%w\n"+color.Red("THERE WAS AN ERROR DELETING YOUR KEYS. They most likely have not been deleted. Proceed with caution."), err) } - fmt.Printf("Deleted the node wallet and all validator keys.\n**Please verify that the keys have been removed by looking at your validator logs before continuing.**\n\n") - fmt.Printf("%sWARNING: If you intend to use these keys for validating again on this or any other machine, you must wait **at least fifteen minutes** after running this command before you can safely begin validating with them again.\nFailure to wait **could cause you to be slashed!**%s\n\n", colorYellow, colorReset) + fmt.Println("Deleted the node wallet and all validator keys.") + fmt.Println("**Please verify that the keys have been removed by looking at your validator logs before continuing.**") + fmt.Println() + color.YellowPrintln("WARNING: If you intend to use these keys for validating again on this or any other machine, you must wait **at least fifteen minutes** after running this command before you can safely begin validating with them again.") + color.YellowPrintln("Failure to wait **could cause you to be slashed!**") + fmt.Println() // Warn about Reverse Hybrid - fmt.Printf("%sNOTE: If you have an externally managed validator client attached to your node (\"reverse hybrid\" mode), those keys *have not been deleted by this process.*%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: If you have an externally managed validator client attached to your node (\"reverse hybrid\" mode), those keys *have not been deleted by this process.*") + fmt.Println() return nil } diff --git a/rocketpool-cli/wallet/recover.go b/rocketpool-cli/wallet/recover.go index 3549ed96e..c2661c085 100644 --- a/rocketpool-cli/wallet/recover.go +++ b/rocketpool-cli/wallet/recover.go @@ -9,6 +9,7 @@ import ( "github.com/urfave/cli" "github.com/rocket-pool/smartnode/shared/services/rocketpool" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" promptcli "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) @@ -38,7 +39,10 @@ func recoverWallet(c *cli.Context) error { } // Prompt a notice about test recovery - fmt.Printf("%sNOTE:\nThis command will fully regenerate your node wallet's private key and (unless explicitly disabled) the validator keys for your minipools.\nIf you just want to test recovery to ensure it works without actually regenerating the files, please use `rocketpool wallet test-recovery` instead.%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE:") + color.YellowPrintln("This command will fully regenerate your node wallet's private key and (unless explicitly disabled) the validator keys for your minipools.") + color.YellowPrintln("If you just want to test recovery to ensure it works without actually regenerating the files, please use `rocketpool wallet test-recovery` instead.") + fmt.Println() // Set password if not set if !status.PasswordSet { @@ -56,7 +60,7 @@ func recoverWallet(c *cli.Context) error { // Handle validator key recovery skipping skipValidatorKeyRecovery := c.Bool("skip-validator-key-recovery") if !skipValidatorKeyRecovery && !ready { - fmt.Printf("%sEth Clients are not available.%s Validator keys cannot be recovered until they are synced and ready.\n", colorYellow, colorReset) + fmt.Println(color.Yellow("Eth Clients are not available.") + " Validator keys cannot be recovered until they are synced and ready.") fmt.Println("You can recover them later with 'rocketpool wallet rebuild'") if !promptcli.Confirm("Would you like to skip recovering the validator keys, and recover the node wallet only?") { fmt.Println("Cancelled.") diff --git a/rocketpool-cli/wallet/status.go b/rocketpool-cli/wallet/status.go index c0d9d101d..0d946f596 100644 --- a/rocketpool-cli/wallet/status.go +++ b/rocketpool-cli/wallet/status.go @@ -8,6 +8,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool" cliutils "github.com/rocket-pool/smartnode/shared/utils/cli" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) func getStatus(c *cli.Context) error { @@ -38,12 +39,12 @@ func getStatus(c *cli.Context) error { emptyAddress := common.Address{} if status.IsMasquerading { if status.NodeAddress != emptyAddress { - fmt.Printf("The node wallet is initialized, but you are currently masquerading as %s%s%s\n", colorBlue, status.AccountAddress, colorReset) + fmt.Printf("The node wallet is initialized, but you are currently masquerading as %s\n", color.LightBlue(status.AccountAddress.Hex())) fmt.Printf("Wallet Address: %s\n", status.NodeAddress) - fmt.Printf("%sDue to this mismatch, the node cannot submit transactions. Use the command 'rocketpool wallet end-masquerade' to end masquerading and restore your wallet address.%s", colorYellow, colorReset) + color.YellowPrintln("Due to this mismatch, the node cannot submit transactions. Use the command 'rocketpool wallet end-masquerade' to end masquerading and restore your wallet address.") } else { - fmt.Printf("The node wallet has not been initialized, but you are currently masquerading as %s%s%s\n", colorBlue, status.AccountAddress, colorReset) - fmt.Printf("%sThe node cannot submit transactions. Use the command 'rocketpool wallet end-masquerade' to end masquerading.%s", colorYellow, colorReset) + fmt.Printf("The node wallet has not been initialized, but you are currently masquerading as %s\n", color.LightBlue(status.AccountAddress.Hex())) + color.YellowPrintln("The node cannot submit transactions. Use the command 'rocketpool wallet end-masquerade' to end masquerading.") } } else { // Not Masquerading diff --git a/rocketpool-cli/wallet/test.go b/rocketpool-cli/wallet/test.go index 9035f5a62..1fef45691 100644 --- a/rocketpool-cli/wallet/test.go +++ b/rocketpool-cli/wallet/test.go @@ -9,14 +9,7 @@ import ( "github.com/urfave/cli" "github.com/rocket-pool/smartnode/shared/services/rocketpool" -) - -const ( - colorReset string = "\033[0m" - colorRed string = "\033[31m" - colorGreen string = "\033[32m" - colorYellow string = "\033[33m" - colorBlue string = "\033[36m" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) func testRecovery(c *cli.Context) error { @@ -35,7 +28,10 @@ func testRecovery(c *cli.Context) error { } // Prompt a notice about test recovery - fmt.Printf("%sNOTE:\nThis command will test the recovery of your node wallet's private key and (unless explicitly disabled) the validator keys for your minipools, but will not actually write any files; it's simply a \"dry run\" of recovery.\nUse `rocketpool wallet recover` to actually recover the wallet and validator keys.%s\n\n", colorYellow, colorReset) + color.YellowPrintln("NOTE:") + color.YellowPrintln("This command will test the recovery of your node wallet's private key and (unless explicitly disabled) the validator keys for your minipools, but will not actually write any files; it's simply a \"dry run\" of recovery.") + color.YellowPrintln("Use `rocketpool wallet recover` to actually recover the wallet and validator keys.") + fmt.Println() // Prompt for mnemonic var mnemonic string diff --git a/rocketpool-cli/wallet/utils.go b/rocketpool-cli/wallet/utils.go index 86429a966..1a341d981 100644 --- a/rocketpool-cli/wallet/utils.go +++ b/rocketpool-cli/wallet/utils.go @@ -17,13 +17,11 @@ import ( "github.com/rocket-pool/smartnode/shared/services/passwords" "github.com/rocket-pool/smartnode/shared/services/rocketpool" "github.com/rocket-pool/smartnode/shared/types/api" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" promptcli "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" hexutils "github.com/rocket-pool/smartnode/shared/utils/hex" ) -const bold string = "\033[1m" -const unbold string = "\033[0m" - // Prompt for a wallet password func promptPassword() string { for { @@ -45,7 +43,7 @@ func promptPassword() string { func PromptMnemonic() string { for { lengthInput := promptcli.Prompt( - "Please enter the "+bold+"number"+unbold+" of words in your mnemonic phrase (24 by default):", + "Please enter the "+color.Bold("number")+" of words in your mnemonic phrase (24 by default):", "^[1-9][0-9]*$", "Please enter a valid number.") @@ -63,7 +61,7 @@ func PromptMnemonic() string { i := 0 for mv.Filled() == false { - prompt := fmt.Sprintf("Enter %sWord Number %d%s of your mnemonic:", bold, i+1, unbold) + prompt := fmt.Sprintf("Enter %s of your mnemonic:", color.BoldSprintf("word number %d", i+1)) word := promptcli.PromptPassword(prompt, "^[a-zA-Z]+$", "Please enter a single word only.") if err := mv.AddWord(strings.ToLower(word)); err != nil { @@ -124,7 +122,11 @@ func promptForCustomKeyPasswords(rp *rocketpool.Client, cfg *config.RocketPoolCo // Prompt the user with a warning message if !testOnly { - fmt.Printf("%sWARNING:\nThe Smart Node has detected that you have custom (externally-derived) validator keys for your minipools.\nIf these keys were actively used for validation by a service such as Allnodes, you MUST CONFIRM WITH THAT SERVICE that they have stopped validating and disabled those keys, and will NEVER validate with them again.\nOtherwise, you may both run the same keys at the same time which WILL RESULT IN YOUR VALIDATORS BEING SLASHED.%s\n\n", colorRed, colorReset) + color.RedPrintln("WARNING:") + color.RedPrintln("The Smart Node has detected that you have custom (externally-derived) validator keys for your minipools.") + color.RedPrintln("If these keys were actively used for validation by a service such as Allnodes, you MUST CONFIRM WITH THAT SERVICE that they have stopped validating and disabled those keys, and will NEVER validate with them again.") + color.RedPrintln("Otherwise, you may both run the same keys at the same time which WILL RESULT IN YOUR VALIDATORS BEING SLASHED.") + fmt.Println() if !promptcli.Confirm("Please confirm that you have coordinated with the service that was running your minipool validators previously to ensure they have STOPPED validation for your minipools, will NEVER start them again, and you have manually confirmed on a Blockchain explorer such as https://beaconcha.in that your minipools are no longer attesting.") { fmt.Println("Cancelled.") diff --git a/shared/services/ec-manager.go b/shared/services/ec-manager.go index cc454f311..a6fc89def 100644 --- a/shared/services/ec-manager.go +++ b/shared/services/ec-manager.go @@ -16,6 +16,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/config" "github.com/rocket-pool/smartnode/shared/types/api" cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" + clicolor "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/log" ) @@ -386,9 +387,7 @@ func (p *ExecutionClientManager) CheckStatus(cfg *config.RocketPoolConfig) *api. expectedChainID := cfg.Smartnode.GetChainID() if status.FallbackClientStatus.Error == "" && status.FallbackClientStatus.NetworkId != expectedChainID { p.fallbackReady = false - colorReset := "\033[0m" - colorYellow := "\033[33m" - status.FallbackClientStatus.Error = fmt.Sprintf("The fallback client is using a different chain [%s%s%s, Chain ID %d] than what your node is configured for [%s, Chain ID %d]", colorYellow, getNetworkNameFromId(status.FallbackClientStatus.NetworkId), colorReset, status.FallbackClientStatus.NetworkId, getNetworkNameFromId(expectedChainID), expectedChainID) + status.FallbackClientStatus.Error = fmt.Sprintf("The fallback client is using a different chain [%s, Chain ID %d] than what your node is configured for [%s, Chain ID %d]", clicolor.Yellow(getNetworkNameFromId(status.FallbackClientStatus.NetworkId)), status.FallbackClientStatus.NetworkId, getNetworkNameFromId(expectedChainID), expectedChainID) return status } } diff --git a/shared/services/gas/gas.go b/shared/services/gas/gas.go index 2202edfd1..f456ef431 100644 --- a/shared/services/gas/gas.go +++ b/shared/services/gas/gas.go @@ -11,13 +11,10 @@ import ( "github.com/rocket-pool/smartnode/shared/services/config" "github.com/rocket-pool/smartnode/shared/services/gas/etherscan" rpsvc "github.com/rocket-pool/smartnode/shared/services/rocketpool" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" ) -const colorReset string = "\033[0m" -const colorYellow string = "\033[33m" -const colorBlue string = "\033[36m" - // DefaultPriorityFeeGwei is the default priority fee in gwei used for automatic transactions const DefaultPriorityFeeGwei float64 = 0.01 @@ -75,7 +72,7 @@ func GetMaxFeeAndLimit(gasInfo rocketpool.GasInfo, rp *rpsvc.Client, headless bo if maxPriorityFeeGwei == 0 { maxPriorityFee := eth.GweiToWei(cfg.Smartnode.PriorityFee.Value.(float64)) if maxPriorityFee == nil || maxPriorityFee.Uint64() == 0 { - fmt.Printf("%sNOTE: max priority fee not set or set to 0, defaulting to 2 gwei%s\n", colorYellow, colorReset) + color.YellowPrintln("NOTE: max priority fee not set or set to 0, defaulting to 2 gwei") maxPriorityFeeGwei = 2 } else { maxPriorityFeeGwei = eth.WeiToGwei(maxPriorityFee) @@ -84,7 +81,7 @@ func GetMaxFeeAndLimit(gasInfo rocketpool.GasInfo, rp *rpsvc.Client, headless bo // Use the requested max fee and priority fee if provided if maxFeeGwei != 0 { - fmt.Printf("%sUsing the requested max fee of %.2f gwei (including a max priority fee of %.2f gwei).\n", colorYellow, maxFeeGwei, maxPriorityFeeGwei) + color.YellowPrintf("Using the requested max fee of %.2f gwei (including a max priority fee of %.2f gwei).\n", maxFeeGwei, maxPriorityFeeGwei) var lowLimit float64 var highLimit float64 @@ -95,7 +92,7 @@ func GetMaxFeeAndLimit(gasInfo rocketpool.GasInfo, rp *rpsvc.Client, headless bo lowLimit = maxFeeGwei / eth.WeiPerGwei * float64(gasLimit) highLimit = lowLimit } - fmt.Printf("Total cost: %.4f to %.4f ETH%s\n", lowLimit, highLimit, colorReset) + color.YellowPrintf("Total cost: %.4f to %.4f ETH\n", lowLimit, highLimit) } else { if headless { @@ -113,12 +110,13 @@ func GetMaxFeeAndLimit(gasInfo rocketpool.GasInfo, rp *rpsvc.Client, headless bo } } - fmt.Printf("%sUsing a max fee of %.3f gwei and a priority fee of %.3f gwei.\n%s", colorBlue, maxFeeGwei, maxPriorityFeeGwei, colorReset) + color.LightBluePrintf("Using a max fee of %.3f gwei and a priority fee of %.3f gwei.\n", maxFeeGwei, maxPriorityFeeGwei) } // Use the requested gas limit if provided if gasLimit != 0 { - fmt.Printf("Using the requested gas limit of %d units.\n%sNOTE: if you set this too low, your transaction may fail but you will still have to pay the gas fee!%s\n", gasLimit, colorYellow, colorReset) + fmt.Printf("Using the requested gas limit of %d units.\n", gasLimit) + color.YellowPrintln("NOTE: if you set this too low, your transaction may fail but you will still have to pay the gas fee!") } if maxPriorityFeeGwei > maxFeeGwei { @@ -134,7 +132,9 @@ func GetMaxFeeAndLimit(gasInfo rocketpool.GasInfo, rp *rpsvc.Client, headless bo } response, err := rp.GetEthBalance() if err != nil { - fmt.Printf("%sWARNING: couldn't check the ETH balance of the node (%s)\nPlease ensure your node wallet has enough ETH to pay for this transaction.%s\n\n", colorYellow, err.Error(), colorReset) + color.YellowPrintf("WARNING: couldn't check the ETH balance of the node (%s)\n", err.Error()) + color.YellowPrintln("Please ensure your node wallet has enough ETH to pay for this transaction.") + fmt.Println() } else if response.Balance.Cmp(ethRequired) < 0 { return Gas{}, fmt.Errorf("Your node has %.6f ETH in its wallet, which is not enough to pay for this transaction with a max fee of %.4f gwei; you require at least %.6f more ETH.", eth.WeiToEth(response.Balance), maxFeeGwei, eth.WeiToEth(big.NewInt(0).Sub(ethRequired, response.Balance))) } @@ -153,7 +153,8 @@ func GetHeadlessMaxFeeWeiWithLatestBlock(cfg *config.RocketPoolConfig, rp *rocke // Getting the latest block to estimate the gas price latestBlock, err := rp.Client.HeaderByNumber(context.Background(), nil) if err != nil { - fmt.Printf("%sWarning: couldn't get gas estimates from the latest block%s\nUsing gas oracles%s\n", colorYellow, err.Error(), colorReset) + color.YellowPrintf("Warning: couldn't get gas estimates from the latest block: %s\n", err.Error()) + color.YellowPrintln("Using gas oracles") } // Get the latest block gas + 20% gasPrice := big.NewInt(0).Add(latestBlock.BaseFee, big.NewInt(0).Div(big.NewInt(0).Mul(latestBlock.BaseFee, big.NewInt(20)), big.NewInt(100))) @@ -208,15 +209,16 @@ func handleEtherscanGasPrices(gasSuggestion etherscan.GasFeeSuggestion, gasInfo slowHighLimit = slowLowLimit } - fmt.Printf("%s+============== Suggested Gas Prices ===============+\n", colorBlue) - fmt.Println("| Speed | Max Fee | Total Gas Cost |") - fmt.Printf("| Fast | %-9s | %.6f to %.6f ETH |\n", + color.LightBluePrintln("+============== Suggested Gas Prices ===============+") + color.LightBluePrintln("| Speed | Max Fee | Total Gas Cost |") + color.LightBluePrintf("| Fast | %-9s | %.6f to %.6f ETH |\n", fmt.Sprintf("%.5f gwei", fastGwei), fastLowLimit, fastHighLimit) - fmt.Printf("| Standard | %-9s | %.6f to %.6f ETH |\n", + color.LightBluePrintf("| Standard | %-9s | %.6f to %.6f ETH |\n", fmt.Sprintf("%.5f gwei", standardGwei), standardLowLimit, standardHighLimit) - fmt.Printf("| Slow | %-9s | %.6f to %.6f ETH |\n", + color.LightBluePrintf("| Slow | %-9s | %.6f to %.6f ETH |\n", fmt.Sprintf("%.5f gwei", slowGwei), slowLowLimit, slowHighLimit) - fmt.Printf("+====================================================+\n\n%s", colorReset) + color.LightBluePrintln("+====================================================+") + fmt.Println() fmt.Printf("These prices include a maximum priority fee of %.3f gwei.\n", priorityFee) diff --git a/shared/services/rocketpool/client.go b/shared/services/rocketpool/client.go index e7eba56fa..5169b024b 100644 --- a/shared/services/rocketpool/client.go +++ b/shared/services/rocketpool/client.go @@ -29,6 +29,7 @@ import ( "github.com/rocket-pool/smartnode/shared/services/rocketpool/template" "github.com/rocket-pool/smartnode/shared/types/api" cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" + clicolor "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/rp" ) @@ -122,7 +123,10 @@ func checkClientStatus(rp *Client) (bool, error) { // Fallback EC and CC are good if ecMgrStatus.FallbackClientStatus.IsSynced && bcMgrStatus.FallbackClientStatus.IsSynced { - fmt.Printf("%sNOTE: primary clients are not ready, using fallback clients...\n\tPrimary EC status: %s\n\tPrimary CC status: %s%s\n\n", colorYellow, primaryEcStatus, primaryBcStatus, colorReset) + clicolor.YellowPrintln("NOTE: primary clients are not ready, using fallback clients...") + clicolor.YellowPrintf("\tPrimary EC status: %s\n", primaryEcStatus) + clicolor.YellowPrintf("\tPrimary CC status: %s\n", primaryBcStatus) + fmt.Println() rp.SetClientStatusFlags(true, true) return true, nil } @@ -1208,23 +1212,23 @@ func (c *Client) deployTemplates(cfg *config.RocketPoolConfig, rocketpoolDir str // Create the custom keys dir customKeyDir, err := homedir.Expand(filepath.Join(cfg.Smartnode.DataPath.Value.(string), "custom-keys")) if err != nil { - fmt.Printf("%sWARNING: Couldn't expand the custom validator key directory (%s). You will not be able to recover any minipool keys you created outside of the Smart Node until you create the folder manually.%s\n", colorYellow, err.Error(), colorReset) + clicolor.YellowPrintf("WARNING: Couldn't expand the custom validator key directory (%s). You will not be able to recover any minipool keys you created outside of the Smart Node until you create the folder manually.\n", err.Error()) return deployedContainers, nil } err = os.MkdirAll(customKeyDir, 0775) if err != nil { - fmt.Printf("%sWARNING: Couldn't create the custom validator key directory (%s). You will not be able to recover any minipool keys you created outside of the Smart Node until you create the folder [%s] manually.%s\n", colorYellow, err.Error(), customKeyDir, colorReset) + clicolor.YellowPrintf("WARNING: Couldn't create the custom validator key directory (%s). You will not be able to recover any minipool keys you created outside of the Smart Node until you create the folder [%s] manually.\n", err.Error(), customKeyDir) } // Create the rewards file dir rewardsFileDir, err := homedir.Expand(cfg.Smartnode.GetRewardsTreeDirectory(false)) if err != nil { - fmt.Printf("%sWARNING: Couldn't expand the rewards tree file directory (%s). You will not be able to view or claim your rewards until you create the folder manually.%s\n", colorYellow, err.Error(), colorReset) + clicolor.YellowPrintf("WARNING: Couldn't expand the rewards tree file directory (%s). You will not be able to view or claim your rewards until you create the folder manually.\n", err.Error()) return deployedContainers, nil } err = os.MkdirAll(rewardsFileDir, 0775) if err != nil { - fmt.Printf("%sWARNING: Couldn't create the rewards tree file directory (%s). You will not be able to view or claim your rewards until you create the folder [%s] manually.%s\n", colorYellow, err.Error(), rewardsFileDir, colorReset) + clicolor.YellowPrintf("WARNING: Couldn't create the rewards tree file directory (%s). You will not be able to view or claim your rewards until you create the folder [%s] manually.\n", err.Error(), rewardsFileDir) } return c.composeAddons(cfg, rocketpoolDir, deployedContainers) diff --git a/shared/services/rocketpool/gas.go b/shared/services/rocketpool/gas.go index 5ad300fcf..e5bc9ee91 100644 --- a/shared/services/rocketpool/gas.go +++ b/shared/services/rocketpool/gas.go @@ -1,19 +1,10 @@ package rocketpool -import ( - "fmt" -) - -const ( - colorReset string = "\033[0m" - colorYellow string = "\033[33m" -) +import "github.com/rocket-pool/smartnode/shared/utils/cli/color" // Print a warning about the gas estimate for operations that have multiple transactions func (rp *Client) PrintMultiTxWarning() { - fmt.Printf("%sNOTE: This operation requires multiple transactions.\n%s", - colorYellow, - colorReset) + color.YellowPrintln("NOTE: This operation requires multiple transactions.") } diff --git a/shared/types/api/node.go b/shared/types/api/node.go index 16c333ecf..34b87116a 100644 --- a/shared/types/api/node.go +++ b/shared/types/api/node.go @@ -11,6 +11,7 @@ import ( "github.com/rocket-pool/smartnode/bindings/tokens" rptypes "github.com/rocket-pool/smartnode/bindings/types" "github.com/rocket-pool/smartnode/shared/services/rewards" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/rp" ) @@ -147,20 +148,16 @@ func (n NodeAlert) Severity() string { } func (n NodeAlert) ColorString() string { - const ( - colorReset string = "\033[0m" - colorRed string = "\033[31m" - colorYellow string = "\033[33m" - ) suppressed := "" if n.IsSuppressed() { suppressed = " (suppressed)" } - alertColor := colorYellow + alertColor := color.Yellow if n.Severity() == "critical" { - alertColor = colorRed + alertColor = color.Red } - return fmt.Sprintf("%s%s%s%s %s: %s", alertColor, n.Severity(), suppressed, colorReset, n.Summary(), n.Description()) + header := alertColor(fmt.Sprintf("%s%s", n.Severity(), suppressed)) + return fmt.Sprintf("%s %s: %s", header, n.Summary(), n.Description()) } type CanRegisterNodeResponse struct { diff --git a/shared/utils/cli/color/color.go b/shared/utils/cli/color/color.go new file mode 100644 index 000000000..8a95a012f --- /dev/null +++ b/shared/utils/cli/color/color.go @@ -0,0 +1,105 @@ +package color + +import ( + "fmt" + "strings" +) + +const colorReset string = "\033[0m" +const colorBold string = "\033[1m" +const colorRed string = "\033[31m" +const colorGreen string = "\033[32m" +const colorYellow string = "\033[33m" +const colorLightBlue string = "\033[36m" + +func color(color, msg string) string { + return color + msg + colorReset +} + +func colorPrintln(colorFunc func(string) string, msgs ...string) { + colorized := make([]string, len(msgs)) + for i, msg := range msgs { + colorized[i] = colorFunc(msg) + } + fmt.Println(strings.Join(colorized, " ")) +} + +func Red(msg string) string { + return color(colorRed, msg) +} + +func RedPrintln(msgs ...string) { + colorPrintln(Red, msgs...) +} + +func RedPrintf(format string, a ...any) { + fmt.Printf(Red(format), a...) +} + +func RedSprintf(format string, a ...any) string { + return color(colorRed, fmt.Sprintf(format, a...)) +} + +func Green(msg string) string { + return color(colorGreen, msg) +} + +func GreenPrintln(msgs ...string) { + colorPrintln(Green, msgs...) +} + +func GreenPrintf(format string, a ...any) { + fmt.Printf(Green(format), a...) +} + +func GreenSprintf(format string, a ...any) string { + return color(colorGreen, fmt.Sprintf(format, a...)) +} + +func Yellow(msg string) string { + return color(colorYellow, msg) +} + +func YellowPrintln(msgs ...string) { + colorPrintln(Yellow, msgs...) +} + +func YellowPrintf(format string, a ...any) { + fmt.Printf(Yellow(format), a...) +} + +func YellowSprintf(format string, a ...any) string { + return color(colorYellow, fmt.Sprintf(format, a...)) +} + +func LightBlue(msg string) string { + return color(colorLightBlue, msg) +} + +func LightBluePrintln(msgs ...string) { + colorPrintln(LightBlue, msgs...) +} + +func LightBluePrintf(format string, a ...any) { + fmt.Printf(LightBlue(format), a...) +} + +func LightBlueSprintf(format string, a ...any) string { + return color(colorLightBlue, fmt.Sprintf(format, a...)) +} + +func Bold(msg string) string { + return color(colorBold, msg) +} + +func BoldPrintln(msgs ...string) { + colorPrintln(Bold, msgs...) +} + +func BoldPrintf(format string, a ...any) { + fmt.Printf(Bold(format), a...) +} + +func BoldSprintf(format string, a ...any) string { + return color(colorBold, fmt.Sprintf(format, a...)) +} diff --git a/shared/utils/cli/migration/import-key.go b/shared/utils/cli/migration/import-key.go index 9d023a48b..7395ddf58 100644 --- a/shared/utils/cli/migration/import-key.go +++ b/shared/utils/cli/migration/import-key.go @@ -6,21 +6,22 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/rocket-pool/smartnode/rocketpool-cli/wallet" "github.com/rocket-pool/smartnode/shared/services/rocketpool" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/rocket-pool/smartnode/shared/utils/cli/prompt" "github.com/urfave/cli" ) -const ( - colorReset string = "\033[0m" - colorRed string = "\033[31m" - colorYellow string = "\033[33m" -) - // Imports the private key for a vacant minipool's validator func ImportKey(c *cli.Context, rp *rocketpool.Client, minipoolAddress common.Address, mnemonic string) bool { // Print a warning and prompt for confirmation of anti-slashing - fmt.Printf("%sWARNING:\nBefore doing this, you **MUST** do the following:\n1. Remove this key from your existing Validator Client used for solo staking\n2. Restart it so that it is no longer validating with that key\n3. Wait for 15 minutes so it has missed at least two attestations\nFailure to do this **will result in your validator being SLASHED**.%s\n\n", colorRed, colorReset) + color.RedPrintln("WARNING:") + color.RedPrintln("Before doing this, you **MUST** do the following:") + color.RedPrintln("1. Remove this key from your existing Validator Client used for solo staking") + color.RedPrintln("2. Restart it so that it is no longer validating with that key") + color.RedPrintln("3. Wait for 15 minutes so it has missed at least two attestations") + color.RedPrintln("Failure to do this **will result in your validator being SLASHED**.") + fmt.Println() if !prompt.Confirm("Have you removed the key from your own Validator Client, restarted it, and waited long enough for your validator to miss at least two attestations?") { fmt.Println("Cancelled.") return false @@ -49,7 +50,10 @@ func ImportKey(c *cli.Context, rp *rocketpool.Client, minipoolAddress common.Add fmt.Print("Restarting Validator Client... ") _, err := rp.RestartVc() if err != nil { - fmt.Printf("failed!\n%sWARNING: error restarting validator client: %s\n\nPlease restart it manually so it picks up the new validator key for your minipool.%s", colorYellow, err.Error(), colorReset) + fmt.Println("failed!") + color.YellowPrintf("WARNING: error restarting validator client: %s\n", err.Error()) + fmt.Println() + color.YellowPrintln("Please restart it manually so it picks up the new validator key for your minipool.") return false } fmt.Println("done!") diff --git a/shared/utils/cli/prompt/prompt.go b/shared/utils/cli/prompt/prompt.go index f749b1692..7110fb1de 100644 --- a/shared/utils/cli/prompt/prompt.go +++ b/shared/utils/cli/prompt/prompt.go @@ -9,13 +9,9 @@ import ( "regexp" "strconv" "strings" -) -const colorReset string = "\033[0m" -const colorRed string = "\033[31m" -const colorGreen string = "\033[32m" -const colorYellow string = "\033[33m" -const colorLightBlue string = "\033[36m" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" +) // Prompt for user input func Prompt(initialPrompt string, expectedFormat string, incorrectFormatPrompt string) string { @@ -43,6 +39,20 @@ func Confirm(fmtStr string, args ...any) bool { return (strings.ToLower(response[:1]) == "y") } +func confirmColor(colorFunc func(string) string, fmtStr string, args ...any) bool { + initialPrompt := fmt.Sprintf(fmtStr, args...) + response := Prompt(fmt.Sprintf("%s [y/n]", colorFunc(initialPrompt)), "(?i)^(y|yes|n|no)$", "Please answer 'y' or 'n'") + return (strings.ToLower(response[:1]) == "y") +} + +func ConfirmRed(fmtStr string, args ...any) bool { + return confirmColor(color.Red, fmtStr, args...) +} + +func ConfirmYellow(fmtStr string, args ...any) bool { + return confirmColor(color.Yellow, fmtStr, args...) +} + // Prompt for 'I agree' confirmation (used on important questions to avoid a quick 'y' response from the user) func ConfirmWithIAgree(fmtStr string, args ...any) bool { initialPrompt := fmt.Sprintf(fmtStr, args...) @@ -81,7 +91,7 @@ func Select(initialPrompt string, options []string) (int, string) { // Prompts the user to verify that there is nobody looking over their shoulder before printing sensitive information. func ConfirmSecureSession(warning string) bool { - if !Confirm("%s%s%s\nAre you sure you want to continue?", colorYellow, warning, colorReset) { + if !Confirm("%s\nAre you sure you want to continue?", color.Yellow(warning)) { fmt.Println("Cancelled.") return false } diff --git a/shared/utils/cli/utils.go b/shared/utils/cli/utils.go index 2b921fb44..e301b9a67 100644 --- a/shared/utils/cli/utils.go +++ b/shared/utils/cli/utils.go @@ -8,34 +8,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/rocket-pool/smartnode/shared/services/rocketpool" cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" ) -const colorReset string = "\033[0m" -const colorRed string = "\033[31m" -const colorGreen string = "\033[32m" -const colorYellow string = "\033[33m" -const colorLightBlue string = "\033[36m" - -func Red(msg string) string { - return color(colorRed, msg) -} - -func Green(msg string) string { - return color(colorGreen, msg) -} - -func Yellow(msg string) string { - return color(colorYellow, msg) -} - -func LightBlue(msg string) string { - return color(colorLightBlue, msg) -} - -func color(color, msg string) string { - return fmt.Sprintf("%s%s%s", color, msg, colorReset) -} - // Print a TX's details to the console. func PrintTransactionHash(rp *rocketpool.Client, hash common.Hash) { @@ -55,11 +30,10 @@ func PrintTransactionHashNoCancel(rp *rocketpool.Client, hash common.Hash) { // Print a warning to the console if the user set a custom nonce, but this operation involves multiple transactions func PrintMultiTransactionNonceWarning() { - fmt.Printf("%sNOTE: You have specified the `nonce` flag to indicate a custom nonce for this transaction.\n"+ - "However, this operation requires multiple transactions.\n"+ - "Rocket Pool will use your custom value as a basis, and increment it for each additional transaction.\n"+ - "If you have multiple pending transactions, this MAY OVERRIDE more than the one that you specified.%s\n\n", colorYellow, colorReset) - + color.YellowPrintln("NOTE: You have specified the `nonce` flag to indicate a custom nonce for this transaction.") + color.YellowPrintln("However, this operation requires multiple transactions.") + color.YellowPrintln("Rocket Pool will use your custom value as a basis, and increment it for each additional transaction.") + color.YellowPrintln("If you have multiple pending transactions, this MAY OVERRIDE more than the one that you specified.") } // Implementation of PrintTransactionHash and PrintTransactionHashNoCancel @@ -133,19 +107,19 @@ func PrettyPrintError(err error) { // Prints an error message when the Beacon client is not using the deposit contract address that Rocket Pool expects func PrintDepositMismatchError(rpNetwork, beaconNetwork uint64, rpDepositAddress, beaconDepositAddress common.Address) { - fmt.Printf("%s***ALERT***\n", colorRed) - fmt.Println("YOUR ETH2 CLIENT IS NOT CONNECTED TO THE SAME NETWORK THAT ROCKET POOL IS USING!") - fmt.Println("This is likely because your ETH2 client is using the wrong configuration.") - fmt.Println("For the safety of your funds, Rocket Pool will not let you deposit your ETH until this is resolved.") - fmt.Println() - fmt.Println("To fix it if you are in Docker mode:") - fmt.Println("\t1. Run 'rocketpool service install -d' to get the latest configuration") - fmt.Println("\t2. Run 'rocketpool service stop' and 'rocketpool service start' to apply the configuration.") - fmt.Println("If you are using Hybrid or Native mode, please correct the network flags in your ETH2 launch script.") - fmt.Println() - fmt.Println("Details:") - fmt.Printf("\tRocket Pool expects deposit contract %s on chain %d.\n", rpDepositAddress.Hex(), rpNetwork) - fmt.Printf("\tYour Beacon client is using deposit contract %s on chain %d.%s\n", beaconDepositAddress.Hex(), beaconNetwork, colorReset) + color.RedPrintln("***ALERT***") + color.RedPrintln("YOUR ETH2 CLIENT IS NOT CONNECTED TO THE SAME NETWORK THAT ROCKET POOL IS USING!") + color.RedPrintln("This is likely because your ETH2 client is using the wrong configuration.") + color.RedPrintln("For the safety of your funds, Rocket Pool will not let you deposit your ETH until this is resolved.") + color.RedPrintln() + color.RedPrintln("To fix it if you are in Docker mode:") + color.RedPrintln("\t1. Run 'rocketpool service install -d' to get the latest configuration") + color.RedPrintln("\t2. Run 'rocketpool service stop' and 'rocketpool service start' to apply the configuration.") + color.RedPrintln("If you are using Hybrid or Native mode, please correct the network flags in your ETH2 launch script.") + color.RedPrintln() + color.RedPrintln("Details:") + color.RedPrintf("\tRocket Pool expects deposit contract %s on chain %d.\n", rpDepositAddress.Hex(), rpNetwork) + color.RedPrintf("\tYour Beacon client is using deposit contract %s on chain %d.\n", beaconDepositAddress.Hex(), beaconNetwork) } // Prints what network you're currently on @@ -154,16 +128,23 @@ func PrintNetwork(currentNetwork cfgtypes.Network, isNew bool) error { return fmt.Errorf("Settings file not found. Please run `rocketpool service config` to set up your Smart Node.") } + var networkName string + switch currentNetwork { case cfgtypes.Network_Mainnet: - fmt.Printf("Your Smart Node is currently using the %sEthereum Mainnet.%s\n\n", colorGreen, colorReset) + networkName = color.Green("Ethereum Mainnet") case cfgtypes.Network_Devnet: - fmt.Printf("Your Smart Node is currently using the %sDevelopment Network.%s\n\n", colorYellow, colorReset) + networkName = color.Yellow("Development Network") case cfgtypes.Network_Testnet: - fmt.Printf("Your Smart Node is currently using the %sHoodi Test Network.%s\n\n", colorYellow, colorReset) + networkName = color.Yellow("Hoodi Test Network") default: - fmt.Printf("%sYou are on an unexpected network [%v].%s\n\n", colorYellow, currentNetwork, colorReset) + color.YellowPrintf("You are on an unexpected network [%v].\n", currentNetwork) + fmt.Println() + return nil } + fmt.Printf("Your Smart Node is currently using the %s.\n", networkName) + fmt.Println() + return nil } diff --git a/treegen/main.go b/treegen/main.go index 463286253..0faae7a12 100644 --- a/treegen/main.go +++ b/treegen/main.go @@ -7,13 +7,12 @@ import ( "runtime/pprof" "github.com/felixge/fgprof" + "github.com/rocket-pool/smartnode/shared/utils/cli/color" "github.com/urfave/cli/v2" ) const ( - version string = "1.5.2" - colorReset string = "\033[0m" - colorRed string = "\033[31m" + version string = "1.5.2" ) func main() { @@ -124,12 +123,12 @@ func main() { if cpuprofile != "" { f, err := os.Create(cpuprofile) if err != nil { - fmt.Printf("%sError generating tree: %s%s\n", colorRed, err.Error(), colorReset) + color.RedPrintf("Error generating tree: %s\n", err.Error()) os.Exit(1) } defer f.Close() if err := pprof.StartCPUProfile(f); err != nil { - fmt.Printf("%sError generating tree: %s%s\n", colorRed, err.Error(), colorReset) + color.RedPrintf("Error generating tree: %s\n", err.Error()) os.Exit(1) } defer pprof.StopCPUProfile() @@ -140,13 +139,13 @@ func main() { defer func() { f, err := os.Create(memprofile) if err != nil { - fmt.Printf("%sError saving heap profile: %s%s\n", colorRed, err.Error(), colorReset) + color.RedPrintf("Error saving heap profile: %s\n", err.Error()) os.Exit(1) } defer f.Close() runtime.GC() if err := pprof.WriteHeapProfile(f); err != nil { - fmt.Printf("%sError saving heap profile: %s%s\n", colorRed, err.Error(), colorReset) + color.RedPrintf("Error saving heap profile: %s\n", err.Error()) } }() } @@ -155,7 +154,7 @@ func main() { if fgprofile != "" { f, err := os.Create(fgprofile) if err != nil { - fmt.Printf("%sError saving heap profile: %s%s\n", colorRed, err.Error(), colorReset) + color.RedPrintf("Error saving heap profile: %s\n", err.Error()) os.Exit(1) } closure := fgprof.Start(f, fgprof.FormatPprof) @@ -174,7 +173,7 @@ func main() { fmt.Println("") err := app.Run(os.Args) if err != nil { - fmt.Printf("%sError generating tree: %s%s\n", colorRed, err.Error(), colorReset) + color.RedPrintf("Error generating tree: %s\n", err.Error()) os.Exit(1) } fmt.Println("")