Skip to content
Merged
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7
github.com/prysmaticlabs/prysm/v5 v5.0.3
github.com/rivo/tview v0.0.0-20230208211350-7dfff1ce7854
github.com/rocket-pool/rocketpool-go v1.8.4-0.20241122223132-c5f2be18f72b
github.com/rocket-pool/rocketpool-go v1.8.4-0.20250512200217-48e87a4a441b
github.com/sethvargo/go-password v0.2.0
github.com/shirou/gopsutil/v3 v3.23.1
github.com/tyler-smith/go-bip39 v1.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -633,8 +633,8 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rocket-pool/go-merkletree v1.0.1-0.20220406020931-c262d9b976dd h1:p9KuetSKB9nte9I/MkkiM3pwKFVQgqxxPTQ0y56Ff6s=
github.com/rocket-pool/go-merkletree v1.0.1-0.20220406020931-c262d9b976dd/go.mod h1:UE9fof8P7iESVtLn1K9CTSkNRYVFHZHlf96RKbU33kA=
github.com/rocket-pool/rocketpool-go v1.8.4-0.20241122223132-c5f2be18f72b h1:PnL2c1StqHDOjyOUYn4C/tuwhLtIZ2N/3qfNYyQlVWc=
github.com/rocket-pool/rocketpool-go v1.8.4-0.20241122223132-c5f2be18f72b/go.mod h1:f2TVsMOYmCwaJOhshG2zRoX89PZmvCkCD7UYJ9waRkI=
github.com/rocket-pool/rocketpool-go v1.8.4-0.20250512200217-48e87a4a441b h1:T6h7a9JyhL+kfQDUKsWphwR7PJizthWxpZzpjN8SGCo=
github.com/rocket-pool/rocketpool-go v1.8.4-0.20250512200217-48e87a4a441b/go.mod h1:f2TVsMOYmCwaJOhshG2zRoX89PZmvCkCD7UYJ9waRkI=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
Expand Down
6 changes: 2 additions & 4 deletions rocketpool-cli/node/primary-withdrawal-address.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli"

"github.com/rocket-pool/rocketpool-go/utils/eth"
"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"
Expand Down Expand Up @@ -73,8 +72,7 @@ func setPrimaryWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string)
if err != nil {
return fmt.Errorf("Invalid test amount '%s': %w\n", inputAmount, err)
}
amountWei := eth.EthToWei(testAmount)
canSendResponse, err := rp.CanNodeSend(amountWei, "eth", withdrawalAddress)
canSendResponse, err := rp.CanNodeSend(testAmount, "eth", withdrawalAddress)
if err != nil {
return err
}
Expand All @@ -90,7 +88,7 @@ func setPrimaryWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string)
return nil
}

sendResponse, err := rp.NodeSend(amountWei, "eth", withdrawalAddress)
sendResponse, err := rp.NodeSend(testAmount, "eth", withdrawalAddress)
if err != nil {
return err
}
Expand Down
5 changes: 2 additions & 3 deletions rocketpool-cli/node/rpl-withdrawal-address.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ func setRPLWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string) erro
if err != nil {
return fmt.Errorf("Invalid test amount '%s': %w\n", inputAmount, err)
}
amountWei := eth.EthToWei(testAmount)
canSendResponse, err := rp.CanNodeSend(amountWei, "eth", withdrawalAddress)
canSendResponse, err := rp.CanNodeSend(testAmount, "eth", withdrawalAddress)
if err != nil {
return err
}
Expand All @@ -101,7 +100,7 @@ func setRPLWithdrawalAddress(c *cli.Context, withdrawalAddressOrENS string) erro
return nil
}

sendResponse, err := rp.NodeSend(amountWei, "eth", withdrawalAddress)
sendResponse, err := rp.NodeSend(testAmount, "eth", withdrawalAddress)
if err != nil {
return err
}
Expand Down
23 changes: 9 additions & 14 deletions rocketpool-cli/node/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ import (
"strings"

"github.com/ethereum/go-ethereum/common"
"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/cli/prompt"
"github.com/rocket-pool/smartnode/shared/utils/math"
)

func nodeSend(c *cli.Context, amount float64, token string, toAddressOrENS string) error {
func nodeSend(c *cli.Context, amountRaw float64, token string, toAddressOrENS string) error {

// Get RP client
rp, err := rocketpool.NewClientFromCtx(c).WithReady()
Expand All @@ -24,9 +22,6 @@ func nodeSend(c *cli.Context, amount float64, token string, toAddressOrENS strin
}
defer rp.Close()

// Get amount in wei
amountWei := eth.EthToWei(amount)

// Get the recipient
var toAddress common.Address
var toAddressString string
Expand All @@ -46,7 +41,7 @@ func nodeSend(c *cli.Context, amount float64, token string, toAddressOrENS strin
}

// Check tokens can be sent
canSend, err := rp.CanNodeSend(amountWei, token, toAddress)
canSend, err := rp.CanNodeSend(amountRaw, token, toAddress)
if err != nil {
return err
}
Expand All @@ -69,16 +64,16 @@ func nodeSend(c *cli.Context, amount float64, token string, toAddressOrENS strin
fmt.Printf("Token address: %s\n", token)
fmt.Printf("Token name: %s\n", canSend.TokenName)
fmt.Printf("Token symbol: %s\n", canSend.TokenSymbol)
fmt.Printf("Node balance: %.6f %s\n\n", eth.WeiToEth(canSend.Balance), 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)

if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf("Are you sure you want to send %.6f of %s to %s? This action cannot be undone!", math.RoundDown(eth.WeiToEth(amountWei), 6), tokenString, toAddressString))) {
if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf("Are you sure you want to send %.8f of %s to %s? This action cannot be undone!", amountRaw, tokenString, toAddressString))) {
fmt.Println("Cancelled.")
return nil
}
} else {
fmt.Printf("Node balance: %.6f %s\n\n", eth.WeiToEth(canSend.Balance), token)
if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf("Are you sure you want to send %.6f %s to %s? This action cannot be undone!", math.RoundDown(eth.WeiToEth(amountWei), 6), token, toAddressString))) {
fmt.Printf("Node balance: %.8f %s\n\n", canSend.Balance, token)
if !(c.Bool("yes") || prompt.Confirm(fmt.Sprintf("Are you sure you want to send %.8f %s to %s? This action cannot be undone!", amountRaw, token, toAddressString))) {
fmt.Println("Cancelled.")
return nil
}
Expand All @@ -91,7 +86,7 @@ func nodeSend(c *cli.Context, amount float64, token string, toAddressOrENS strin
}

// Send tokens
response, err := rp.NodeSend(amountWei, token, toAddress)
response, err := rp.NodeSend(amountRaw, token, toAddress)
if err != nil {
return err
}
Expand All @@ -108,9 +103,9 @@ func nodeSend(c *cli.Context, amount float64, token string, toAddressOrENS strin

// Log & return
if strings.HasPrefix(token, "0x") {
fmt.Printf("Successfully sent %.6f of %s to %s.\n", math.RoundDown(eth.WeiToEth(amountWei), 6), tokenString, toAddressString)
fmt.Printf("Successfully sent %.6f of %s to %s.\n", amountRaw, tokenString, toAddressString)
} else {
fmt.Printf("Successfully sent %.6f %s to %s.\n", math.RoundDown(eth.WeiToEth(amountWei), 6), token, toAddressString)
fmt.Printf("Successfully sent %.6f %s to %s.\n", amountRaw, token, toAddressString)
}
return nil

Expand Down
8 changes: 4 additions & 4 deletions rocketpool/api/node/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ func RegisterSubcommands(command *cli.Command, name string, aliases []string) {
if err := cliutils.ValidateArgCount(c, 3); err != nil {
return err
}
amountWei, err := cliutils.ValidatePositiveWeiAmount("send amount", c.Args().Get(0))
amountRaw, err := cliutils.ValidatePositiveEthAmount("send amount", c.Args().Get(0))
if err != nil {
return err
}
Expand All @@ -865,7 +865,7 @@ func RegisterSubcommands(command *cli.Command, name string, aliases []string) {
}

// Run
api.PrintResponse(canNodeSend(c, amountWei, token, toAddress))
api.PrintResponse(canNodeSend(c, amountRaw, token, toAddress))
return nil

},
Expand All @@ -881,7 +881,7 @@ func RegisterSubcommands(command *cli.Command, name string, aliases []string) {
if err := cliutils.ValidateArgCount(c, 3); err != nil {
return err
}
amountWei, err := cliutils.ValidatePositiveWeiAmount("send amount", c.Args().Get(0))
amountRaw, err := cliutils.ValidatePositiveEthAmount("send amount", c.Args().Get(0))
if err != nil {
return err
}
Expand All @@ -895,7 +895,7 @@ func RegisterSubcommands(command *cli.Command, name string, aliases []string) {
}

// Run
api.PrintResponse(nodeSend(c, amountWei, token, toAddress))
api.PrintResponse(nodeSend(c, amountRaw, token, toAddress))
return nil

},
Expand Down
40 changes: 25 additions & 15 deletions rocketpool/api/node/send.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package node

import (
"bytes"
"context"
"fmt"
"math/big"
Expand All @@ -16,7 +17,7 @@ import (
"github.com/rocket-pool/smartnode/shared/utils/eth1"
)

func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Address) (*api.CanNodeSendResponse, error) {
func canNodeSend(c *cli.Context, amountRaw float64, token string, to common.Address) (*api.CanNodeSendResponse, error) {

// Get services
if err := services.RequireNodeWallet(c); err != nil {
Expand Down Expand Up @@ -79,6 +80,10 @@ func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Add
if strings.HasPrefix(token, "0x") {
tokenAddress := common.HexToAddress(token)

if bytes.Equal(to.Bytes(), tokenAddress.Bytes()) {
return nil, fmt.Errorf("sending tokens to the same address as the token is prohibited for safety")
}

// Error out if using one of the well-known ones
if tokenAddress == *rplContract.Address {
return nil, fmt.Errorf("sending RPL via the token address is prohibited for safety; please use 'rpl' as the token to send instead of its address")
Expand All @@ -95,6 +100,8 @@ func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Add
if err != nil {
return nil, fmt.Errorf("error creating ERC20 contract binding: %w", err)
}

amountWei := eth.EthToWeiWithDecimals(amountRaw, contract.Decimals)
response.TokenName = contract.Name
response.TokenSymbol = contract.Symbol

Expand All @@ -103,7 +110,8 @@ func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Add
if err != nil {
return nil, fmt.Errorf("error getting ERC20 balance: %w", err)
}
response.Balance = balance

response.Balance = eth.WeiToEthWithDecimals(balance, contract.Decimals)
response.InsufficientBalance = (amountWei.Cmp(balance) > 0)

// Get the gas info
Expand All @@ -114,16 +122,17 @@ func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Add
response.GasInfo = gasInfo
} else {
// Handle well-known token types
amountWei := eth.EthToWei(amountRaw)
var balanceWei *big.Int
switch token {
case "eth":

// Check node ETH balance
ethBalanceWei, err := ec.BalanceAt(context.Background(), nodeAccount.Address, nil)
balanceWei, err = ec.BalanceAt(context.Background(), nodeAccount.Address, nil)
if err != nil {
return nil, err
}
response.Balance = ethBalanceWei
response.InsufficientBalance = (amountWei.Cmp(ethBalanceWei) > 0)
response.InsufficientBalance = (amountWei.Cmp(balanceWei) > 0)
gasInfo, err := eth.EstimateSendTransactionGas(ec, to, nil, false, opts)
if err != nil {
return nil, err
Expand All @@ -137,12 +146,11 @@ func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Add
return nil, err
}
// Check node RPL balance
rplBalanceWei, err := tokens.GetRPLBalance(rp, nodeAccount.Address, nil)
balanceWei, err = tokens.GetRPLBalance(rp, nodeAccount.Address, nil)
if err != nil {
return nil, err
}
response.Balance = rplBalanceWei
response.InsufficientBalance = (amountWei.Cmp(rplBalanceWei) > 0)
response.InsufficientBalance = (amountWei.Cmp(balanceWei) > 0)
gasInfo, err := tokens.EstimateTransferRPLGas(rp, to, amountWei, opts)
if err != nil {
return nil, err
Expand All @@ -156,12 +164,11 @@ func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Add
return nil, err
}
// Check node fixed-supply RPL balance
fixedSupplyRplBalanceWei, err := tokens.GetFixedSupplyRPLBalance(rp, nodeAccount.Address, nil)
balanceWei, err = tokens.GetFixedSupplyRPLBalance(rp, nodeAccount.Address, nil)
if err != nil {
return nil, err
}
response.Balance = fixedSupplyRplBalanceWei
response.InsufficientBalance = (amountWei.Cmp(fixedSupplyRplBalanceWei) > 0)
response.InsufficientBalance = (amountWei.Cmp(balanceWei) > 0)
gasInfo, err := tokens.EstimateTransferFixedSupplyRPLGas(rp, to, amountWei, opts)
if err != nil {
return nil, err
Expand All @@ -175,19 +182,19 @@ func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Add
return nil, err
}
// Check node rETH balance
rethBalanceWei, err := tokens.GetRETHBalance(rp, nodeAccount.Address, nil)
balanceWei, err = tokens.GetRETHBalance(rp, nodeAccount.Address, nil)
if err != nil {
return nil, err
}
response.Balance = rethBalanceWei
response.InsufficientBalance = (amountWei.Cmp(rethBalanceWei) > 0)
response.InsufficientBalance = (amountWei.Cmp(balanceWei) > 0)
gasInfo, err := tokens.EstimateTransferRETHGas(rp, to, amountWei, opts)
if err != nil {
return nil, err
}
response.GasInfo = gasInfo

}
response.Balance = eth.WeiToEth(balanceWei)
}

// Update & return response
Expand All @@ -196,7 +203,7 @@ func canNodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Add

}

func nodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Address) (*api.NodeSendResponse, error) {
func nodeSend(c *cli.Context, amountRaw float64, token string, to common.Address) (*api.NodeSendResponse, error) {

// Get services
if err := services.RequireNodeWallet(c); err != nil {
Expand Down Expand Up @@ -238,12 +245,15 @@ func nodeSend(c *cli.Context, amountWei *big.Int, token string, to common.Addres
return nil, fmt.Errorf("error creating ERC20 contract binding: %w", err)
}

amountWei := eth.EthToWeiWithDecimals(amountRaw, contract.Decimals)

tx, err := contract.Transfer(to, amountWei, opts)
if err != nil {
return nil, err
}
response.TxHash = tx.Hash()
} else {
amountWei := eth.EthToWei(amountRaw)
// Handle token type
switch token {
case "eth":
Expand Down
8 changes: 4 additions & 4 deletions shared/services/rocketpool/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,8 +601,8 @@ func (c *Client) NodeDeposit(amountWei *big.Int, minFee float64, salt *big.Int,
}

// Check whether the node can send tokens
func (c *Client) CanNodeSend(amountWei *big.Int, token string, toAddress common.Address) (api.CanNodeSendResponse, error) {
responseBytes, err := c.callAPI(fmt.Sprintf("node can-send %s %s %s", amountWei.String(), token, toAddress.Hex()))
func (c *Client) CanNodeSend(amountRaw float64, token string, toAddress common.Address) (api.CanNodeSendResponse, error) {
responseBytes, err := c.callAPI(fmt.Sprintf("node can-send %.10f %s %s", amountRaw, token, toAddress.Hex()))
if err != nil {
return api.CanNodeSendResponse{}, fmt.Errorf("Could not get can node send status: %w", err)
}
Expand All @@ -617,8 +617,8 @@ func (c *Client) CanNodeSend(amountWei *big.Int, token string, toAddress common.
}

// Send tokens from the node to an address
func (c *Client) NodeSend(amountWei *big.Int, token string, toAddress common.Address) (api.NodeSendResponse, error) {
responseBytes, err := c.callAPI(fmt.Sprintf("node send %s %s %s", amountWei.String(), token, toAddress.Hex()))
func (c *Client) NodeSend(amountRaw float64, token string, toAddress common.Address) (api.NodeSendResponse, error) {
responseBytes, err := c.callAPI(fmt.Sprintf("node send %.10f %s %s", amountRaw, token, toAddress.Hex()))
if err != nil {
return api.NodeSendResponse{}, fmt.Errorf("Could not send tokens from node: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion shared/types/api/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ type CreateVacantMinipoolResponse struct {
type CanNodeSendResponse struct {
Status string `json:"status"`
Error string `json:"error"`
Balance *big.Int `json:"balance"`
Balance float64 `json:"balance"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should really switch to something like this at some point:
https://pkg.go.dev/github.com/shopspring/decimal

TokenName string `json:"name"`
TokenSymbol string `json:"symbol"`
CanSend bool `json:"canSend"`
Expand Down
Loading