Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rETH handling #243

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions rocketpool-cli/node/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,40 @@ func RegisterCommands(app *cli.App, name string, aliases []string) {
},
},

{
Name: "eth-to-reth",
Aliases: []string{"e2r"},
Usage: "Swap ETH to rETH",
UsageText: "rocketpool node eth-to-reth [options]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "amount, a",
Usage: "The amount of ETH to swap to rETH (or 'max' which keeps ~0.1 ETH in your wallet to pay for gas in future transactions)",
},
cli.BoolFlag{
Name: "yes, y",
Usage: "Automatically confirm ETH conversion",
},
},
Action: func(c *cli.Context) error {

// Validate args
if err := cliutils.ValidateArgCount(c, 0); err != nil {
return err
}

// Validate flags
if c.String("amount") != "" && c.String("amount") != "all" {
if _, err := cliutils.ValidatePositiveEthAmount("swap amount", c.String("amount")); err != nil {
return err
}
}

// Run
return nodeSwapToReth(c)
},
},

{
Name: "set-voting-delegate",
Aliases: []string{"sv"},
Expand Down
3 changes: 2 additions & 1 deletion rocketpool-cli/node/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ func getStatus(c *cli.Context) error {
// Account address & balances
fmt.Printf("%s=== Account and Balances ===%s\n", colorGreen, colorReset)
fmt.Printf(
"The node %s%s%s has a balance of %.6f ETH and %.6f RPL.\n",
"The node %s%s%s has a balance of %.6f ETH, %.6f rETH and %.6f RPL.\n",
colorBlue,
status.AccountAddress.Hex(),
colorReset,
math.RoundDown(eth.WeiToEth(status.AccountBalances.ETH), 6),
math.RoundDown(eth.WeiToEth(status.AccountBalances.RETH), 6),
math.RoundDown(eth.WeiToEth(status.AccountBalances.RPL), 6))
if status.AccountBalances.FixedSupplyRPL.Cmp(big.NewInt(0)) > 0 {
fmt.Printf("The node has a balance of %.6f old RPL which can be swapped for new RPL.\n", math.RoundDown(eth.WeiToEth(status.AccountBalances.FixedSupplyRPL), 6))
Expand Down
152 changes: 152 additions & 0 deletions rocketpool-cli/node/swap-reth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package node

import (
"fmt"
"math/big"
"strconv"

"github.com/rocket-pool/rocketpool-go/utils/eth"
"github.com/urfave/cli"

"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/math"
)

func nodeSwapToReth(c *cli.Context) error {

// Get RP client
rp, err := rocketpool.NewClientFromCtx(c)
if err != nil {
return err
}
defer rp.Close()

// Check and assign the EC status
err = cliutils.CheckClientStatus(rp)
if err != nil {
return err
}

// Get swap amount
var amountWei *big.Int

if c.String("amount") == "max" {

// get data
nodeStatus, err := rp.NodeStatus()
if err != nil {
return err
}
queueStatus, err := rp.QueueStatus()
if err != nil {
return err
}

var availableAmountWeiWithGasBuffer big.Int
if availableAmountWeiWithGasBuffer.Sub(nodeStatus.AccountBalances.ETH, eth.EthToWei(0.1)).Sign() == -1 {
return fmt.Errorf("You need at least 0.1 ETH to be able to pay gas for future transactions.")
}
maxAmount := availableAmountWeiWithGasBuffer
if availableAmountWeiWithGasBuffer.Cmp(queueStatus.MaxDepositPoolBalance.Sub(queueStatus.MaxDepositPoolBalance, queueStatus.DepositPoolBalance)) > 0 {
maxAmount = *queueStatus.MaxDepositPoolBalance
}
amountWei = &maxAmount

} else if c.String("amount") != "" {

// Parse amount
swapAmount, err := strconv.ParseFloat(c.String("amount"), 64)
if err != nil {
return fmt.Errorf("Invalid swap amount '%s': %w", c.String("amount"), err)
}
amountWei = eth.EthToWei(swapAmount)

} else {

nodeStatus, err := rp.NodeStatus()
if err != nil {
return err
}
queueStatus, err := rp.QueueStatus()
if err != nil {
return err
}

var maxAmount big.Int
maxAmount.Sub(nodeStatus.AccountBalances.ETH, eth.EthToWei(0.1))
if maxAmount.Sign() == 1 && maxAmount.Cmp(queueStatus.MaxDepositPoolBalance.Sub(queueStatus.MaxDepositPoolBalance, queueStatus.DepositPoolBalance)) > 0 {
maxAmount = *queueStatus.MaxDepositPoolBalance
}

// Prompt for deposit max amount if possible
if maxAmount.Sign() > 0 && cliutils.Confirm(fmt.Sprintf("Would you like to swap the maximum available ETH balance (%.6f ETH) (and keep some ETH to pay for future gas costs)?", math.RoundDown(eth.WeiToEth(&maxAmount), 6))){

amountWei = &maxAmount

} else {

// Prompt for custom amount
inputAmount := cliutils.Prompt("Please enter an amount of ETH to swap. Remember that you will need sufficient ETH to execute future transactions!", "^\\d+(\\.\\d+)?$", "Invalid amount")
swapAmount, err := strconv.ParseFloat(inputAmount, 64)
if err != nil {
return fmt.Errorf("Invalid swap amount '%s': %w", inputAmount, err)
}
amountWei = eth.EthToWei(swapAmount)

}

}

// Check ETH can be swapped
canStake, err := rp.CanStakeEth(amountWei)
if err != nil {
return err
}
if !canStake.CanStake {
fmt.Println("Cannot stake ETH:")
if canStake.InsufficientBalance {
fmt.Println("The node's ETH balance is insufficient.")
}
if canStake.DepositDisabled {
fmt.Println("ETH deposits are currently disabled.")
}
if canStake.BelowMinStakeAmount {
fmt.Println("The stake amount is below the minimum accepted value.")
}
if canStake.DepositPoolFull {
fmt.Println("No space left in deposit pool.")
}
return nil
}
fmt.Println("Stake ETH Gas Info:")
// Assign max fees
err = gas.AssignMaxFeeAndLimit(canStake.GasInfo, rp, c.Bool("yes"))
if err != nil {
return err
}

// Prompt for confirmation
if !(c.Bool("yes") || cliutils.Confirm(fmt.Sprintf("Are you sure you want to stake %.6f ETH for %.6f rETH?", math.RoundDown(eth.WeiToEth(amountWei), 6), math.RoundDown(eth.WeiToEth(canStake.RethAmount), 6)))) {
fmt.Println("Cancelled.")
return nil
}

// Stake ETH
stakeResponse, err := rp.StakeEth(amountWei)
if err != nil {
return err
}

fmt.Printf("Staking ETH...\n")
cliutils.PrintTransactionHash(rp, stakeResponse.StakeTxHash)
if _, err = rp.WaitForTransaction(stakeResponse.StakeTxHash); err != nil {
return err
}

// Log & return
fmt.Printf("Successfully staked %.6f ETH in return for %.6f rETH.\n", math.RoundDown(eth.WeiToEth(amountWei), 6), math.RoundDown(eth.WeiToEth(canStake.RethAmount), 6))
return nil

}
43 changes: 43 additions & 0 deletions rocketpool/api/node/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,49 @@ func RegisterSubcommands(command *cli.Command, name string, aliases []string) {
},
},

{
Name: "can-stake-eth",
Usage: "Check whether the node can swap ETH to rETH via the deposit pool",
UsageText: "rocketpool api node can-stake-eth amount",
Action: func(c *cli.Context) error {

// Validate args
if err := cliutils.ValidateArgCount(c, 1); err != nil {
return err
}
amountWei, err := cliutils.ValidatePositiveWeiAmount("swap amount", c.Args().Get(0))
if err != nil {
return err
}

// Run
api.PrintResponse(canSwapEth(c, amountWei))
return nil

},
},
{
Name: "stake-eth",
Usage: "Swap ETH to rETH via the deposit pool",
UsageText: "rocketpool api node stake-eth amount",
Action: func(c *cli.Context) error {

// Validate args
if err := cliutils.ValidateArgCount(c, 1); err != nil {
return err
}
amountWei, err := cliutils.ValidatePositiveWeiAmount("swap amount", c.Args().Get(0))
if err != nil {
return err
}

// Run
api.PrintResponse(swapEth(c, amountWei))
return nil

},
},

{
Name: "sign",
Usage: "Signs a transaction with the node's private key. The TX must be serialized as a hex string.",
Expand Down
Loading