github.com/0xsequence/ethkit@v1.25.0/cmd/ethkit/balance.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  	"net/url"
     9  	"strconv"
    10  
    11  	"github.com/spf13/cobra"
    12  
    13  	"github.com/0xsequence/ethkit/ethrpc"
    14  	"github.com/0xsequence/ethkit/go-ethereum/common"
    15  	"github.com/0xsequence/ethkit/go-ethereum/params"
    16  )
    17  
    18  const (
    19  	flagBalanceBlock = "block"
    20  	flagBalanceEther = "ether"
    21  	flagBalanceRpcUrl = "rpc-url"
    22  )
    23  
    24  func init() {
    25  	rootCmd.AddCommand(NewBalanceCmd())
    26  }
    27  
    28  func NewBalanceCmd() *cobra.Command {
    29  	c := &balance{}
    30  	cmd := &cobra.Command{
    31  		Use:   "balance [account]",
    32  		Short: "Get the balance of an account",
    33  		Aliases: []string{"b"},
    34  		Args:  cobra.ExactArgs(1),
    35  		RunE:  c.Run,
    36  	}
    37  
    38  	cmd.Flags().StringP(flagBalanceBlock, "B", "latest", "The block height to query at")
    39  	cmd.Flags().BoolP(flagBalanceEther, "e", false, "Format the balance in ether")
    40  	cmd.Flags().StringP(flagBalanceRpcUrl, "r", "", "The RPC endpoint to the blockchain node to interact with")
    41  
    42  	return cmd
    43  }
    44  
    45  type balance struct {
    46  }
    47  
    48  func (c *balance) Run(cmd *cobra.Command, args []string) error {
    49  	fAccount := cmd.Flags().Args()[0]
    50  	fBlock, err := cmd.Flags().GetString(flagBalanceBlock)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	fEther, err := cmd.Flags().GetBool(flagBalanceEther)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	fRpc, err := cmd.Flags().GetString(flagBalanceRpcUrl)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	if !common.IsHexAddress(fAccount) {
    64  		return errors.New("error: please provide a valid account address (e.g. 0x213a286A1AF3Ac010d4F2D66A52DeAf762dF7742)")
    65  	}
    66  
    67  	if _, err = url.ParseRequestURI(fRpc); err != nil {
    68  		return errors.New("error: please provide a valid rpc url (e.g. https://nodes.sequence.app/mainnet)")
    69  	}
    70  
    71  	provider, err := ethrpc.NewProvider(fRpc)
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	block, err := strconv.ParseUint(fBlock, 10, 64)
    77  	if err != nil {
    78  		// TODO: implement support for all tags: earliest, latest, pending, finalized, safe
    79  		if fBlock == "latest" {
    80  			bh, err := provider.BlockNumber(context.Background())
    81  			if err != nil {
    82  				return err
    83  			}
    84  			block = bh
    85  		} else {
    86  			return errors.New("error: invalid block height")
    87  		}
    88  	}
    89  
    90  	wei, err := provider.BalanceAt(context.Background(), common.HexToAddress(fAccount), big.NewInt(int64(block)))
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	if fEther {
    96  		bal := weiToEther(wei)
    97  		fmt.Fprintln(cmd.OutOrStdout(), bal, "ether")
    98  	} else {
    99  		fmt.Fprintln(cmd.OutOrStdout(), wei, "wei")
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // https://github.com/ethereum/go-ethereum/issues/21221
   106  func weiToEther(wei *big.Int) *big.Float {
   107  	f := new(big.Float)
   108  	f.SetPrec(236) //  IEEE 754 octuple-precision binary floating-point format: binary256
   109  	f.SetMode(big.ToNearestEven)
   110  	fWei := new(big.Float)
   111  	fWei.SetPrec(236) //  IEEE 754 octuple-precision binary floating-point format: binary256
   112  	fWei.SetMode(big.ToNearestEven)
   113  
   114  	return f.Quo(fWei.SetInt(wei), big.NewFloat(params.Ether))
   115  }