You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

329 lines
7.1 KiB

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "io/ioutil"
  6. "math"
  7. "math/big"
  8. "os"
  9. "strings"
  10. "github.com/ethereum/go-ethereum/accounts"
  11. "github.com/ethereum/go-ethereum/accounts/keystore"
  12. "github.com/ethereum/go-ethereum/common"
  13. "github.com/ethereum/go-ethereum/core/types"
  14. "github.com/ethereum/go-ethereum/ethclient"
  15. "github.com/spf13/viper"
  16. "github.com/urfave/cli"
  17. )
  18. var cfg Config
  19. type Config struct {
  20. Web3 struct {
  21. Url string
  22. }
  23. KeyStore struct {
  24. Path string
  25. Address string
  26. Password string
  27. KeyJsonPath string
  28. }
  29. }
  30. var cliCommands = []cli.Command{
  31. {
  32. Name: "info",
  33. Aliases: []string{},
  34. Usage: "get info",
  35. Action: cmdInfo,
  36. },
  37. {
  38. Name: "sendall",
  39. Aliases: []string{},
  40. Usage: "send all eth to address",
  41. Action: cmdSendAll,
  42. },
  43. }
  44. func cmdInfo(c *cli.Context) error {
  45. if err := mustRead(c); err != nil {
  46. return err
  47. }
  48. ks, acc := loadKeyStore()
  49. ethSrv := loadWeb3(ks, &acc)
  50. balance, err := ethSrv.GetBalance(acc.Address)
  51. if err != nil {
  52. fmt.Println("error getting balance")
  53. return err
  54. }
  55. fmt.Println("Current balance " + balance.String() + " ETH")
  56. return nil
  57. }
  58. func cmdSendAll(c *cli.Context) error {
  59. if err := mustRead(c); err != nil {
  60. return err
  61. }
  62. toAddrStr := c.GlobalString("address")
  63. if toAddrStr == "" {
  64. return fmt.Errorf("no address to send the ETH specified")
  65. }
  66. fmt.Println("Sending to:", toAddrStr)
  67. ks, acc := loadKeyStore()
  68. ethSrv := loadWeb3(ks, &acc)
  69. toAddr := common.HexToAddress(toAddrStr)
  70. if err := ethSrv.SendAll(toAddr); err != nil {
  71. return err
  72. }
  73. return nil
  74. }
  75. func main() {
  76. app := cli.NewApp()
  77. app.Name = "recover-geth-funds"
  78. app.Usage = "cli to send all the funds from a geth keystore to an address"
  79. app.Version = "0.0.1"
  80. app.Flags = []cli.Flag{
  81. cli.StringFlag{Name: "config"},
  82. &cli.StringFlag{Name: "address"},
  83. }
  84. app.Commands = []cli.Command{}
  85. app.Commands = append(app.Commands, cliCommands...)
  86. err := app.Run(os.Args)
  87. if err != nil {
  88. fmt.Println(err)
  89. os.Exit(3)
  90. }
  91. }
  92. // load
  93. const (
  94. passwdPrefix = "passwd:"
  95. filePrefix = "file:"
  96. )
  97. func Assert(msg string, err error) {
  98. if err != nil {
  99. fmt.Println(msg, " ", err.Error())
  100. os.Exit(1)
  101. }
  102. }
  103. func mustRead(c *cli.Context) error {
  104. viper.SetConfigType("yaml")
  105. viper.SetConfigName("config")
  106. viper.AddConfigPath(".") // adding home directory as first search path
  107. if c.GlobalString("config") != "" {
  108. viper.SetConfigFile(c.GlobalString("config"))
  109. }
  110. if err := viper.ReadInConfig(); err != nil {
  111. return err
  112. }
  113. err := viper.Unmarshal(&cfg)
  114. if err != nil {
  115. return err
  116. }
  117. return nil
  118. }
  119. func loadKeyStore() (*keystore.KeyStore, accounts.Account) {
  120. var err error
  121. var passwd string
  122. ks := keystore.NewKeyStore(cfg.KeyStore.Path, keystore.StandardScryptN, keystore.StandardScryptP)
  123. if strings.HasPrefix(cfg.KeyStore.Password, passwdPrefix) {
  124. passwd = cfg.KeyStore.Password[len(passwdPrefix):]
  125. } else {
  126. filename := cfg.KeyStore.Password
  127. if strings.HasPrefix(filename, filePrefix) {
  128. filename = cfg.KeyStore.Password[len(filePrefix):]
  129. }
  130. passwdbytes, err := ioutil.ReadFile(filename)
  131. Assert("Cannot read password ", err)
  132. passwd = string(passwdbytes)
  133. }
  134. acc, err := ks.Find(accounts.Account{
  135. Address: common.HexToAddress(cfg.KeyStore.Address),
  136. })
  137. Assert("Cannot find keystore account", err)
  138. Assert("Cannot unlock account", ks.Unlock(acc, passwd))
  139. fmt.Println("Keystore and account unlocked successfully:", acc.Address.Hex())
  140. return ks, acc
  141. }
  142. func loadWeb3(ks *keystore.KeyStore, acc *accounts.Account) *ethService {
  143. // Create geth client
  144. url := cfg.Web3.Url
  145. hidden := strings.HasPrefix(url, "hidden:")
  146. if hidden {
  147. url = url[len("hidden:"):]
  148. }
  149. passwd, err := readPassword(cfg.KeyStore.Password)
  150. if err != nil {
  151. fmt.Println(err)
  152. os.Exit(0)
  153. }
  154. ethsrv := newEthService(url, ks, acc, cfg.KeyStore.KeyJsonPath, passwd)
  155. if hidden {
  156. fmt.Println("Connection to web3 server opened", "url", "(hidden)")
  157. } else {
  158. fmt.Println("Connection to web3 server opened", "url", cfg.Web3.Url)
  159. }
  160. return ethsrv
  161. }
  162. func readPassword(configPassword string) (string, error) {
  163. var passwd string
  164. if strings.HasPrefix(cfg.KeyStore.Password, passwdPrefix) {
  165. passwd = cfg.KeyStore.Password[len(passwdPrefix):]
  166. } else {
  167. filename := cfg.KeyStore.Password
  168. if strings.HasPrefix(filename, filePrefix) {
  169. filename = cfg.KeyStore.Password[len(filePrefix):]
  170. }
  171. passwdbytes, err := ioutil.ReadFile(filename)
  172. if err != nil {
  173. return passwd, err
  174. }
  175. passwd = string(passwdbytes)
  176. }
  177. return passwd, nil
  178. }
  179. // eth
  180. type ethService struct {
  181. ks *keystore.KeyStore
  182. acc *accounts.Account
  183. client *ethclient.Client
  184. KeyStore struct {
  185. Path string
  186. Password string
  187. }
  188. }
  189. func newEthService(url string, ks *keystore.KeyStore, acc *accounts.Account, keystorePath, password string) *ethService {
  190. client, err := ethclient.Dial(url)
  191. if err != nil {
  192. fmt.Println("Can not open connection to web3 (config.Web3.Url: " + url + ")\n" + err.Error() + "\n")
  193. os.Exit(0)
  194. }
  195. service := &ethService{
  196. ks: ks,
  197. acc: acc,
  198. client: client,
  199. KeyStore: struct {
  200. Path string
  201. Password string
  202. }{
  203. Path: keystorePath,
  204. Password: password,
  205. },
  206. }
  207. return service
  208. }
  209. func (ethSrv *ethService) GetBalance(address common.Address) (*big.Float, error) {
  210. balance, err := ethSrv.client.BalanceAt(context.Background(), address, nil)
  211. if err != nil {
  212. return nil, err
  213. }
  214. ethBalance := gweiToEth(balance)
  215. return ethBalance, nil
  216. }
  217. func gweiToEth(g *big.Int) *big.Float {
  218. f := new(big.Float)
  219. f.SetString(g.String())
  220. e := new(big.Float).Quo(f, big.NewFloat(math.Pow10(18)))
  221. return e
  222. }
  223. func (ethSrv *ethService) SendAll(toAddr common.Address) error {
  224. balance, err := ethSrv.client.BalanceAt(context.Background(), ethSrv.acc.Address, nil)
  225. if err != nil {
  226. return err
  227. }
  228. ethBalance := gweiToEth(balance)
  229. fmt.Println("Current balance:", ethBalance, "ETH")
  230. nonce, err := ethSrv.client.PendingNonceAt(context.Background(), ethSrv.acc.Address)
  231. if err != nil {
  232. return err
  233. }
  234. fmt.Println("Nonce:", nonce)
  235. gasLimit := uint64(21000)
  236. gasLimitBI := big.NewInt(int64(gasLimit))
  237. gasPrice, err := ethSrv.client.SuggestGasPrice(context.Background())
  238. if err != nil {
  239. return err
  240. }
  241. value := new(big.Int).Sub(balance, new(big.Int).Mul(gasLimitBI, gasPrice))
  242. fmt.Println("balance", gweiToEth(balance), "ETH")
  243. fmt.Println(" tosend", gweiToEth(value), "ETH (substracting the fees)")
  244. confirmed := askConfirmation()
  245. if !confirmed {
  246. return fmt.Errorf("operation cancelled")
  247. }
  248. fmt.Println("operation confirmed")
  249. var data []byte
  250. tx := types.NewTransaction(nonce, toAddr, value, gasLimit, gasPrice, data)
  251. chainID, err := ethSrv.client.NetworkID(context.Background())
  252. if err != nil {
  253. return err
  254. }
  255. signedTx, err := ethSrv.ks.SignTx(*ethSrv.acc, tx, chainID)
  256. if err != nil {
  257. return err
  258. }
  259. err = ethSrv.client.SendTransaction(context.Background(), signedTx)
  260. if err != nil {
  261. return err
  262. }
  263. fmt.Printf("tx sent: %s", signedTx.Hash().Hex())
  264. return nil
  265. }
  266. func askConfirmation() bool {
  267. var s string
  268. fmt.Printf("(y/N): ")
  269. _, err := fmt.Scan(&s)
  270. if err != nil {
  271. panic(err)
  272. }
  273. s = strings.TrimSpace(s)
  274. s = strings.ToLower(s)
  275. if s == "y" || s == "yes" {
  276. return true
  277. }
  278. return false
  279. }