package eth import ( "context" "crypto/ecdsa" "encoding/json" "fmt" "log" "math/big" "strings" "syncscan-go/erc20" "syncscan-go/model" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/pkg/errors" "github.com/ybbus/jsonrpc/v2" "golang.org/x/crypto/sha3" ) type Eth struct { host string client *ethclient.Client RpcClient jsonrpc.RPCClient } var LogTransferSignHash = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)")) func New(rawurl string) (*Eth, error) { client, err := ethclient.Dial(rawurl) if err != nil { fmt.Printf("Failed to connect to eth: %v", err) return nil, err } rpcClient := jsonrpc.NewClient(rawurl) return &Eth{host: rawurl, client: client, RpcClient: rpcClient}, nil } func (this *Eth) GetTxByHash(hash string) (tx model.Tx, err error) { response, err := this.RpcClient.Call("eth_getTransactionByHash", hash) if err != nil { return } log.Printf("%+v\n\n\n", response.Result) err = response.GetObject(&tx) if err != nil { return } return } func (this *Eth) GetLastBlockNumber() (*big.Int, error) { header, err := this.client.HeaderByNumber(context.Background(), nil) if err != nil { return nil, err } return header.Number, nil } func (this *Eth) GetBlockByNumber(number *big.Int) (*types.Block, error) { b, err := this.client.BlockByNumber(context.Background(), number) if err != nil { return nil, err } return b, nil } const ETH = 1_000_000_000_000_000_000 func getFixedReward(blockNo uint64) uint64 { if blockNo < 4370000 { return ETH * 5 } else if blockNo < 7280000 { return ETH * 3 } else { return ETH * 2 } } // 당근을 위해 커스터마이징 된 부분 func getFixedRewardForDangnn(blockNo uint64) uint64 { return ETH * 3 } func (this *Eth) GetReward(b *types.Block, rts types.Receipts) (blockReward *big.Float, txFee *big.Int, uncleTotalReward *big.Float, err error) { fixedReward := getFixedRewardForDangnn(b.Number().Uint64()) blockReward = big.NewFloat(float64(fixedReward)) txFee = big.NewInt(0) txs := b.Transactions() for i, rt := range rts { gasUsed := big.NewInt(int64(rt.GasUsed)) fee := new(big.Int).Mul(gasUsed, txs[i].GasPrice()) txFee.Add(txFee, fee) } uncleCount := len(b.Uncles()) if uncleCount > 2 { uncleCount = 2 } uncleTotalReward = big.NewFloat(0) if uncleCount > 0 { //블럭 마이너에게 돌아가는 리워드 uncleInclusionFee := (float64(fixedReward) * 0.03125) * float64(uncleCount) for i, u := range b.Uncles() { if i == 2 { break } //엉클블럭 마이너에게 돌아가는 리워드 uncleReward := float64(u.Number.Uint64() - b.Number().Uint64() + 8) uncleReward *= float64(fixedReward / 8) uncleTotalReward.Add(uncleTotalReward, big.NewFloat(uncleReward)) } uncleTotalReward.Add(uncleTotalReward, big.NewFloat(uncleInclusionFee)) } //Fixed_Fee + Tx_Fee + Uncle_Fee blockReward.Add(blockReward, big.NewFloat(float64(txFee.Uint64()))) blockReward.Add(blockReward, uncleTotalReward) return blockReward, txFee, uncleTotalReward, nil } func (this *Eth) GetTxReceipts(transactions types.Transactions) (ret types.Receipts, err error) { for _, tx := range transactions { receipt, err := this.client.TransactionReceipt(context.Background(), tx.Hash()) if err != nil { return nil, err } if receipt != nil { ret = append(ret, receipt) } } return } func (this *Eth) GetTxReceipt(txhash string) (ret *types.Receipt, err error) { receipt, err := this.client.TransactionReceipt(context.Background(), common.HexToHash(txhash)) if err != nil { return } if receipt != nil { ret = receipt } return } func (this *Eth) GetToken(contractAddr common.Address) (token *erc20.Erc20, err error) { token, err = erc20.NewErc20(contractAddr, this.client) return } func (this *Eth) GetUncleBlockByBlockNumberAndIndex(blockNo uint64, idx int) (interface{}, error) { b, err := this.RpcClient.Call("eth_getUncleByBlockNumberAndIndex", fmt.Sprintf("0x%x", blockNo), fmt.Sprintf("0x%x", idx)) if err != nil { return "", err } return b.Result, nil } func (this *Eth) CreateWallet() (privateKey, walletAddr string) { key, err := crypto.GenerateKey() if err != nil { log.Fatal(err) } privateKeyBytes := crypto.FromECDSA(key) privateKey = hexutil.Encode(privateKeyBytes)[2:] publicKey := key.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { log.Fatal("error casting public key to ECDSA") } publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) //log.Println(hexutil.Encode(publicKeyBytes)[4:]) //address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() hash := sha3.NewLegacyKeccak256() hash.Write(publicKeyBytes[1:]) walletAddr = hexutil.Encode(hash.Sum(nil)[12:]) return } func (this *Eth) GetWalletAddressFromPrivateKey(privateKey string) (string, error) { if privateKey[:2] == "0x" { privateKey = privateKey[2:] } pkey, err := crypto.HexToECDSA(privateKey) if err != nil { return "", err } publicKey := pkey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { return "", errors.New("error casting public key to ECDSA") } fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) return fromAddress.Hex(), nil } func (this *Eth) Transfer(privateKey, to string, valueInWei, gasPrice *big.Int, gasLimit uint64) (txHash string, err error) { if privateKey[:2] == "0x" { privateKey = privateKey[2:] } if to[:2] == "0x" { to = to[2:] } pkey, err := crypto.HexToECDSA(privateKey) if err != nil { return } publicKey := pkey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { err = errors.New("error casting public key to ECDSA") } fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) nonce, err := this.getNonce(fromAddress.Hex()) if err != nil { return } toAddress := common.HexToAddress(to) tx := types.NewTransaction(nonce, toAddress, valueInWei, gasLimit, gasPrice, nil) chainID, err := this.client.NetworkID(context.Background()) if err != nil { return } signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), pkey) if err != nil { log.Fatal(err) } err = this.client.SendTransaction(context.Background(), signedTx) if err != nil { return } txHash = signedTx.Hash().Hex() log.Printf("tx sent: %s\n", txHash) return } func (this *Eth) getNonce(fromAddress string) (uint64, error) { resp, err := this.RpcClient.Call("eth_getTransactionCount", fromAddress, "pending") if err != nil { return 0, err } m := resp.Result.(string) nonce, err := hexutil.DecodeUint64(m) return nonce, err } func (this *Eth) GetSuggestedGasPrice() (*big.Int, error) { gasPrice, err := this.client.SuggestGasPrice(context.Background()) return gasPrice, err } func (this *Eth) GetEstimatedGasLimit(tokenAddr, pkey, toAddr string, tokenAmount *big.Int) ([]byte, uint64, error) { if strings.HasPrefix(pkey, "0x") { pkey = pkey[2:] } privateKey, err := crypto.HexToECDSA(pkey) if err != nil { return nil, 0, err } publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { err = errors.New("error casting public key to ECDSA") return nil, 0, err } fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) toAddress := common.HexToAddress(toAddr) tokenAddress := common.HexToAddress(tokenAddr) transferFnSignature := []byte("transfer(address,uint256)") hash := sha3.NewLegacyKeccak256() hash.Write(transferFnSignature) methodID := hash.Sum(nil)[:4] paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32) paddedAmount := common.LeftPadBytes(tokenAmount.Bytes(), 32) var data []byte data = append(data, methodID...) data = append(data, paddedAddress...) data = append(data, paddedAmount...) gasLimit, err := this.client.EstimateGas(context.Background(), ethereum.CallMsg{ From: fromAddress, To: &tokenAddress, Data: data, }) return data, gasLimit, err } func (this *Eth) TransferToken(tokenAddr, pkey, toAddr string, tokenAmount *big.Int, gasPrice *big.Int, gasLimit uint64) (txHash string, err error) { if strings.HasPrefix(pkey, "0x") { pkey = pkey[2:] } privateKey, err := crypto.HexToECDSA(pkey) if err != nil { return "", err } publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { err = errors.New("error casting public key to ECDSA") return "", err } fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) nonce, err := this.getNonce(fromAddress.Hex()) if err != nil { return "", err } value := big.NewInt(0) // in wei (0 eth) tokenAddress := common.HexToAddress(tokenAddr) data, _, err := this.GetEstimatedGasLimit(tokenAddr, pkey, toAddr, tokenAmount) if err != nil { return "", err } v := &transferValue{ nonce: nonce, gasLimit: gasLimit, gasPrice: gasPrice, } log.Printf("nonce: %d, gas price: %d, gas limit: %d\n", nonce, v.gasPrice, v.gasLimit) // 25624 //gasPrice = gasPrice.Add(gasPrice, big.NewInt(100_000_000)) //gasPrice = big.NewInt(5320000000) tx := types.NewTransaction(v.nonce, tokenAddress, value, v.gasLimit, v.gasPrice, data) chainID, err := this.client.NetworkID(context.Background()) if err != nil { return "", err } signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) if err != nil { return "", err } err = this.client.SendTransaction(context.Background(), signedTx) if err != nil { return "", err } return signedTx.Hash().Hex(), nil } func (this *Eth) GetTokenBalance(tokenAddress, address string) (balance *big.Int, err error) { token, err := this.GetToken(common.HexToAddress(tokenAddress)) b, err := token.BalanceOf(nil, common.HexToAddress(address)) //token.Transfer(&bind.TransactOpts{ // From: common.Address{}, // Nonce: nil, // Signer: nil, // Value: nil, // GasPrice: nil, // GasFeeCap: nil, // GasTipCap: nil, // GasLimit: 0, // Context: nil, // NoSend: false, //}, common.HexToAddress(address), big.NewInt(100)) return b, err } func (this *Eth) GetBalance(address string) (balance *big.Int, err error) { resp, err := this.RpcClient.Call("eth_getBalance", address, "latest") if err != nil { return } b, _ := new(big.Int).SetString(resp.Result.(string)[2:], 16) return b, nil } func (this *Eth) GetInternalTxsByHash(blockNo uint64, txHash string) (calls []model.Call, err error) { lastestBlockNo, err := this.GetLastBlockNumber() if err != nil { return } args := make([]interface{}, 0) args = append(args, txHash) args = append(args, map[string]interface { }{ "tracer": "callTracer", "reexec": lastestBlockNo.Uint64() - blockNo + 20, }) resp, err := this.RpcClient.Call("debug_traceTransaction", args) if err != nil { return nil, err } data, _ := json.Marshal(resp.Result) log.Println(string(data)) m, ok := resp.Result.(map[string]interface{}) if !ok { return } data, err = json.Marshal(m["calls"]) if err != nil { return } err = json.Unmarshal(data, &calls) if err != nil { return } return }