github.com/0chain/gosdk@v1.17.11/zcncore/ethwallet_base.go (about)

     1  package zcncore
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"encoding/json"
     7  	"fmt"
     8  	"log"
     9  	"math"
    10  	"math/big"
    11  	"regexp"
    12  	"sync"
    13  
    14  	"github.com/0chain/gosdk/core/tokenrate"
    15  	"github.com/0chain/gosdk/core/zcncrypto"
    16  	hdwallet "github.com/0chain/gosdk/zcncore/ethhdwallet"
    17  	"github.com/ethereum/go-ethereum"
    18  	"github.com/ethereum/go-ethereum/common"
    19  	"github.com/ethereum/go-ethereum/common/hexutil"
    20  	"github.com/ethereum/go-ethereum/core/types"
    21  	"github.com/ethereum/go-ethereum/crypto"
    22  	"github.com/ethereum/go-ethereum/ethclient"
    23  	"github.com/ethereum/go-ethereum/params"
    24  	"golang.org/x/crypto/sha3"
    25  )
    26  
    27  // TODO change to real wallets
    28  const walletAddr = "0xb9EF770B6A5e12E45983C5D80545258aA38F3B78"
    29  const tokenAddress = "0x28b149020d2152179873ec60bed6bf7cd705775d"
    30  
    31  var once sync.Once
    32  
    33  var ethClient *ethclient.Client
    34  
    35  var getEthClient = func() (*ethclient.Client, error) {
    36  	var err error
    37  
    38  	once.Do(func() {
    39  		if len(_config.chain.EthNode) == 0 {
    40  			err = fmt.Errorf("eth node SDK not initialized")
    41  			return
    42  		}
    43  
    44  		logging.Info("requesting from ", _config.chain.EthNode)
    45  		ethClient, err = ethclient.Dial(_config.chain.EthNode)
    46  	})
    47  
    48  	return ethClient, err
    49  }
    50  
    51  // TokensToEth - converting wei to eth tokens
    52  func TokensToEth(tokens int64) float64 {
    53  	fbalance := new(big.Float)
    54  	fbalance.SetString(fmt.Sprint(tokens))
    55  	ethValue, _ := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18))).Float64()
    56  	return ethValue
    57  }
    58  
    59  // TokensToEth - converting eth tokens to wei
    60  func EthToTokens(tokens float64) int64 {
    61  	return int64(tokens * float64(params.Ether))
    62  }
    63  
    64  func GTokensToEth(tokens int64) float64 {
    65  	return float64(tokens) / float64(params.GWei)
    66  }
    67  
    68  func GEthToTokens(gwei float64) int64 {
    69  	return int64(gwei * float64(params.GWei))
    70  }
    71  
    72  // GetWalletAddrFromEthMnemonic - wallet ETH address from mnemoninnc
    73  func GetWalletAddrFromEthMnemonic(mnemonic string) (string, error) {
    74  	wallet, err := hdwallet.NewFromMnemonic(mnemonic)
    75  	if err != nil {
    76  		return "", err
    77  	}
    78  
    79  	path := hdwallet.MustParseDerivationPath("m/44'/60'/0'/0/0")
    80  	account, err := wallet.Derive(path, false)
    81  	if err != nil {
    82  		return "", err
    83  	}
    84  
    85  	privKey, err := wallet.PrivateKeyHex(account)
    86  	if err != nil {
    87  		return "", err
    88  	}
    89  
    90  	type ethWalletinfo struct {
    91  		ID         string `json:"ID"`
    92  		PrivateKey string `json:"PrivateKey"`
    93  	}
    94  
    95  	res, err := json.Marshal(ethWalletinfo{ID: account.Address.Hex(), PrivateKey: privKey})
    96  	return string(res), err
    97  }
    98  
    99  // GetEthBalance - getting back balance for ETH wallet
   100  func GetEthBalance(ethAddr string, cb GetBalanceCallback) error {
   101  	go func() {
   102  		value, err := getBalanceFromEthNode(ethAddr)
   103  		if err != nil {
   104  			logging.Error(err)
   105  			cb.OnBalanceAvailable(StatusError, 0, err.Error())
   106  			return
   107  		}
   108  		cb.OnBalanceAvailable(StatusSuccess, value, "")
   109  	}()
   110  	return nil
   111  }
   112  
   113  // IsValidEthAddress - multiple checks for valid ETH address
   114  func IsValidEthAddress(ethAddr string) (bool, error) {
   115  	client, err := getEthClient()
   116  	if err != nil {
   117  		return false, err
   118  	}
   119  
   120  	return isValidEthAddress(ethAddr, client)
   121  }
   122  
   123  // IsEthTransactionVerified checks if the transaction - given its hash - is verified on the ethereum network
   124  //   - txHash: transaction hash
   125  func IsEthTransactionVerified(txHash string) (bool, error) {
   126  	client, err := getEthClient()
   127  	if err != nil {
   128  		return false, err
   129  	}
   130  
   131  	var (
   132  		tx      *types.Transaction
   133  		pending bool
   134  	)
   135  
   136  	tx, pending, err = client.TransactionByHash(context.Background(), common.HexToHash(txHash))
   137  	if err != nil {
   138  		return false, err
   139  	}
   140  	return tx != nil && !pending, nil
   141  }
   142  
   143  func isValidEthAddress(ethAddr string, client *ethclient.Client) (bool, error) {
   144  	re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$")
   145  	if !re.MatchString(ethAddr) {
   146  		return false, fmt.Errorf("regex error")
   147  	}
   148  
   149  	address := common.HexToAddress(ethAddr)
   150  	balance, err := client.BalanceAt(context.Background(), address, nil)
   151  	if err != nil {
   152  		return false, fmt.Errorf(err.Error())
   153  	}
   154  
   155  	isContract := balance.Int64() > 0
   156  	return isContract, nil
   157  }
   158  
   159  // CreateWalletFromEthMnemonic - creating new wallet from Eth mnemonics
   160  func CreateWalletFromEthMnemonic(mnemonic, password string, statusCb WalletCallback) error {
   161  	if len(_config.chain.Miners) < 1 || len(_config.chain.Sharders) < 1 {
   162  		return fmt.Errorf("SDK not initialized")
   163  	}
   164  	go func() {
   165  		sigScheme := zcncrypto.NewSignatureScheme(_config.chain.SignatureScheme)
   166  		_, err := sigScheme.GenerateKeysWithEth(mnemonic, password)
   167  		if err != nil {
   168  			statusCb.OnWalletCreateComplete(StatusError, "", err.Error())
   169  			return
   170  		}
   171  	}()
   172  	return nil
   173  }
   174  
   175  // CheckEthHashStatus - checking the status of ETH transaction
   176  // possible values 0 or 1
   177  func CheckEthHashStatus(hash string) int {
   178  	txHash := common.HexToHash(hash)
   179  
   180  	var client *ethclient.Client
   181  	var err error
   182  	if client, err = getEthClient(); err != nil {
   183  		return -1
   184  	}
   185  
   186  	tx, err := client.TransactionReceipt(context.Background(), txHash)
   187  	if err != nil {
   188  		return -1
   189  	}
   190  	return int(tx.Status)
   191  }
   192  
   193  // ConvertZcnTokenToETH - converting Zcn tokens to Eth
   194  //   - f: ZCN tokens amount
   195  func ConvertZcnTokenToETH(f float64) (float64, error) {
   196  	ethRate, err := tokenrate.GetUSD(context.TODO(), "eth")
   197  	if err != nil {
   198  		return 0, err
   199  	}
   200  	return f * ethRate, nil
   201  }
   202  
   203  // SuggestEthGasPrice - return back suggested price for gas
   204  func SuggestEthGasPrice() (int64, error) {
   205  	var client *ethclient.Client
   206  	var err error
   207  	if client, err = getEthClient(); err != nil {
   208  		return 0, err
   209  	}
   210  
   211  	gasPrice, err := client.SuggestGasPrice(context.Background())
   212  	if err != nil {
   213  		return 0, err
   214  	}
   215  
   216  	return gasPrice.Int64(), nil
   217  }
   218  
   219  // TransferEthTokens - transfer ETH tokens to multisign wallet
   220  func TransferEthTokens(fromPrivKey string, amountTokens, gasPrice int64) (string, error) {
   221  	var client *ethclient.Client
   222  	var err error
   223  	if client, err = getEthClient(); err != nil {
   224  		return "", err
   225  	}
   226  
   227  	privateKey, err := crypto.HexToECDSA(fromPrivKey)
   228  	if err != nil {
   229  		return "", err
   230  	}
   231  
   232  	publicKey := privateKey.Public()
   233  	publicKeyECDSA, _ := publicKey.(*ecdsa.PublicKey)
   234  
   235  	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
   236  	nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
   237  	if err != nil {
   238  		return "", err
   239  	}
   240  
   241  	toAddress := common.HexToAddress(walletAddr)
   242  	tokenAddress := common.HexToAddress(tokenAddress)
   243  
   244  	transferFnSignature := []byte("transfer(address,uint256)")
   245  	hash := sha3.NewLegacyKeccak256()
   246  	hash.Write(transferFnSignature)
   247  	methodID := hash.Sum(nil)[:4]
   248  	fmt.Println(hexutil.Encode(methodID)) // 0xa9059cbb
   249  
   250  	paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)
   251  	fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d
   252  
   253  	amount := new(big.Int)
   254  	amount.SetInt64(amountTokens)
   255  
   256  	paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
   257  	fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
   258  
   259  	var data []byte
   260  	data = append(data, methodID...)
   261  	data = append(data, paddedAddress...)
   262  	data = append(data, paddedAmount...)
   263  
   264  	gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
   265  		To:   &tokenAddress,
   266  		Data: data,
   267  	})
   268  	if err != nil {
   269  		log.Fatal(err)
   270  	}
   271  
   272  	txData := &types.LegacyTx{
   273  		Nonce:    nonce,
   274  		GasPrice: big.NewInt(gasPrice),
   275  		Gas:      gasLimit,
   276  		To:       &tokenAddress,
   277  		Value:    amount,
   278  		Data:     data,
   279  	}
   280  	tx := types.NewTx(txData)
   281  
   282  	chainID, err := client.ChainID(context.Background())
   283  	if err != nil {
   284  		return "", err
   285  	}
   286  
   287  	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
   288  	if err != nil {
   289  		return "", err
   290  	}
   291  
   292  	err = client.SendTransaction(context.Background(), signedTx)
   293  	if err != nil {
   294  		return "", err
   295  	}
   296  
   297  	return signedTx.Hash().Hex(), nil
   298  }
   299  
   300  func getBalanceFromEthNode(ethAddr string) (int64, error) {
   301  	if client, err := getEthClient(); err == nil {
   302  		account := common.HexToAddress(ethAddr)
   303  		logging.Info("for eth address", account)
   304  		balance, err := client.BalanceAt(context.Background(), account, nil)
   305  		if err != nil {
   306  			return 0, err
   307  		}
   308  
   309  		logging.Info("balance", balance.String())
   310  
   311  		return balance.Int64(), nil
   312  	} else {
   313  		return 0, err
   314  	}
   315  }