package main import ( "context" "fmt" "io/ioutil" "math" "math/big" "os" "strings" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/spf13/viper" "github.com/urfave/cli" ) var cfg Config type Config struct { Web3 struct { Url string } KeyStore struct { Path string Address string Password string KeyJsonPath string } } var cliCommands = []cli.Command{ { Name: "info", Aliases: []string{}, Usage: "get info", Action: cmdInfo, }, { Name: "sendall", Aliases: []string{}, Usage: "send all eth to address", Action: cmdSendAll, }, } func cmdInfo(c *cli.Context) error { if err := mustRead(c); err != nil { return err } ks, acc := loadKeyStore() ethSrv := loadWeb3(ks, &acc) balance, err := ethSrv.GetBalance(acc.Address) if err != nil { fmt.Println("error getting balance") return err } fmt.Println("Current balance " + balance.String() + " ETH") return nil } func cmdSendAll(c *cli.Context) error { if err := mustRead(c); err != nil { return err } toAddrStr := c.GlobalString("address") if toAddrStr == "" { return fmt.Errorf("no address to send the ETH specified") } fmt.Println("Sending to:", toAddrStr) ks, acc := loadKeyStore() ethSrv := loadWeb3(ks, &acc) toAddr := common.HexToAddress(toAddrStr) if err := ethSrv.SendAll(toAddr); err != nil { return err } return nil } func main() { app := cli.NewApp() app.Name = "recover-geth-funds" app.Usage = "cli to send all the funds from a geth keystore to an address" app.Version = "0.0.1" app.Flags = []cli.Flag{ cli.StringFlag{Name: "config"}, &cli.StringFlag{Name: "address"}, } app.Commands = []cli.Command{} app.Commands = append(app.Commands, cliCommands...) err := app.Run(os.Args) if err != nil { fmt.Println(err) os.Exit(3) } } // load const ( passwdPrefix = "passwd:" filePrefix = "file:" ) func Assert(msg string, err error) { if err != nil { fmt.Println(msg, " ", err.Error()) os.Exit(1) } } func mustRead(c *cli.Context) error { viper.SetConfigType("yaml") viper.SetConfigName("config") viper.AddConfigPath(".") // adding home directory as first search path if c.GlobalString("config") != "" { viper.SetConfigFile(c.GlobalString("config")) } if err := viper.ReadInConfig(); err != nil { return err } err := viper.Unmarshal(&cfg) if err != nil { return err } return nil } func loadKeyStore() (*keystore.KeyStore, accounts.Account) { var err error var passwd string ks := keystore.NewKeyStore(cfg.KeyStore.Path, keystore.StandardScryptN, keystore.StandardScryptP) if strings.HasPrefix(cfg.KeyStore.Password, passwdPrefix) { passwd = cfg.KeyStore.Password[len(passwdPrefix):] } else { filename := cfg.KeyStore.Password if strings.HasPrefix(filename, filePrefix) { filename = cfg.KeyStore.Password[len(filePrefix):] } passwdbytes, err := ioutil.ReadFile(filename) Assert("Cannot read password ", err) passwd = string(passwdbytes) } acc, err := ks.Find(accounts.Account{ Address: common.HexToAddress(cfg.KeyStore.Address), }) Assert("Cannot find keystore account", err) Assert("Cannot unlock account", ks.Unlock(acc, passwd)) fmt.Println("Keystore and account unlocked successfully:", acc.Address.Hex()) return ks, acc } func loadWeb3(ks *keystore.KeyStore, acc *accounts.Account) *ethService { // Create geth client url := cfg.Web3.Url hidden := strings.HasPrefix(url, "hidden:") if hidden { url = url[len("hidden:"):] } passwd, err := readPassword(cfg.KeyStore.Password) if err != nil { fmt.Println(err) os.Exit(0) } ethsrv := newEthService(url, ks, acc, cfg.KeyStore.KeyJsonPath, passwd) if hidden { fmt.Println("Connection to web3 server opened", "url", "(hidden)") } else { fmt.Println("Connection to web3 server opened", "url", cfg.Web3.Url) } return ethsrv } func readPassword(configPassword string) (string, error) { var passwd string if strings.HasPrefix(cfg.KeyStore.Password, passwdPrefix) { passwd = cfg.KeyStore.Password[len(passwdPrefix):] } else { filename := cfg.KeyStore.Password if strings.HasPrefix(filename, filePrefix) { filename = cfg.KeyStore.Password[len(filePrefix):] } passwdbytes, err := ioutil.ReadFile(filename) if err != nil { return passwd, err } passwd = string(passwdbytes) } return passwd, nil } // eth type ethService struct { ks *keystore.KeyStore acc *accounts.Account client *ethclient.Client KeyStore struct { Path string Password string } } func newEthService(url string, ks *keystore.KeyStore, acc *accounts.Account, keystorePath, password string) *ethService { client, err := ethclient.Dial(url) if err != nil { fmt.Println("Can not open connection to web3 (config.Web3.Url: " + url + ")\n" + err.Error() + "\n") os.Exit(0) } service := ðService{ ks: ks, acc: acc, client: client, KeyStore: struct { Path string Password string }{ Path: keystorePath, Password: password, }, } return service } func (ethSrv *ethService) GetBalance(address common.Address) (*big.Float, error) { balance, err := ethSrv.client.BalanceAt(context.Background(), address, nil) if err != nil { return nil, err } ethBalance := gweiToEth(balance) return ethBalance, nil } func gweiToEth(g *big.Int) *big.Float { f := new(big.Float) f.SetString(g.String()) e := new(big.Float).Quo(f, big.NewFloat(math.Pow10(18))) return e } func (ethSrv *ethService) SendAll(toAddr common.Address) error { balance, err := ethSrv.client.BalanceAt(context.Background(), ethSrv.acc.Address, nil) if err != nil { return err } ethBalance := gweiToEth(balance) fmt.Println("Current balance:", ethBalance, "ETH") nonce, err := ethSrv.client.PendingNonceAt(context.Background(), ethSrv.acc.Address) if err != nil { return err } fmt.Println("Nonce:", nonce) gasLimit := uint64(21000) gasLimitBI := big.NewInt(int64(gasLimit)) gasPrice, err := ethSrv.client.SuggestGasPrice(context.Background()) if err != nil { return err } value := new(big.Int).Sub(balance, new(big.Int).Mul(gasLimitBI, gasPrice)) fmt.Println("balance", gweiToEth(balance), "ETH") fmt.Println(" tosend", gweiToEth(value), "ETH (substracting the fees)") confirmed := askConfirmation() if !confirmed { return fmt.Errorf("operation cancelled") } fmt.Println("operation confirmed") var data []byte tx := types.NewTransaction(nonce, toAddr, value, gasLimit, gasPrice, data) chainID, err := ethSrv.client.NetworkID(context.Background()) if err != nil { return err } signedTx, err := ethSrv.ks.SignTx(*ethSrv.acc, tx, chainID) if err != nil { return err } err = ethSrv.client.SendTransaction(context.Background(), signedTx) if err != nil { return err } fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) return nil } func askConfirmation() bool { var s string fmt.Printf("(y/N): ") _, err := fmt.Scan(&s) if err != nil { panic(err) } s = strings.TrimSpace(s) s = strings.ToLower(s) if s == "y" || s == "yes" { return true } return false }