package dgn

import (
	"context"
	"crypto/ecdsa"
	"encoding/json"
	"fmt"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ybbus/jsonrpc/v2"
	"log"
	"math/big"
	"net/http"
	"strconv"
	"strings"
	comm "sync-block/common"
	"sync-block/dgn/fourbyte"
	"sync-block/model"
	"sync-block/util"
	//"github.com/cellcrypto/dangnn"
	//"github.com/cellcrypto/dangnn/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/common/math"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/pkg/errors"
	"golang.org/x/crypto/sha3"
)

type Eth struct {
	host      	string
	client    	*ethclient.Client
	RpcClient 	jsonrpc.RPCClient
	conifg 		*comm.BlockConfig
	ByteDb 		*fourbyte.Database
	AbiParsed	*abi.ABI
}

var LogTransferSignHash = crypto.Keccak256Hash([]byte("Transfer(address,address,uint256)"))

// Erc20ABI is the input ABI used to generate the binding from.
const Erc20ABI = "[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name_\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"symbol_\",\"type\":\"string\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"


func New(rawurl string, config *comm.BlockConfig) (*Eth, error) {
	client, err := ethclient.Dial(rawurl)
	if err != nil {
		fmt.Printf("Failed to connect to eth: %v", err)
		return nil, err
	}
	var opts *jsonrpc.RPCClientOpts
	opts = &jsonrpc.RPCClientOpts{
		HTTPClient:   &http.Client{
			Timeout:      util.MustParseDuration("120s"),	// 시간은 100만건당 40초 정도로 설정해야 된다.
		},
		CustomHeaders: nil,
	}

	rpcClient := jsonrpc.NewClientWithOpts(rawurl, opts)

	db, err := fourbyte.New()
	if err != nil {
		fmt.Printf("Failed to Read to 4bytejson: %v", err)
		return nil, err
	}

	parsed, err := abi.JSON(strings.NewReader(Erc20ABI))
	if err != nil {
		fmt.Printf("failed loaded abi parser: %v", err)
		return nil, err
	}

	return &Eth{host: rawurl, client: client, RpcClient: rpcClient, conifg: config, ByteDb: db, AbiParsed: &parsed}, 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
}


type txExtraInfo struct {
	BlockNumber *string         `json:"blockNumber,omitempty"`
	BlockHash   *common.Hash    `json:"blockHash,omitempty"`
	From        *common.Address `json:"from,omitempty"`
}

type rpcTransaction struct {
	tx *types.Transaction
	txExtraInfo
}

func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error {
	if err := json.Unmarshal(msg, &tx.tx); err != nil {
		return err
	}
	return json.Unmarshal(msg, &tx.txExtraInfo)
}

type rpcBlock struct {
	Hash         common.Hash      `json:"hash"`
	Transactions []rpcTransaction `json:"transactions"`
	UncleHashes  []common.Hash    `json:"uncles"`
}


func (this *Eth) GetBlockByNumber(number *big.Int) (*Block, error) {
	rpcResp, err := this.RpcClient.Call("eth_getBlockByNumber", fmt.Sprintf("0x%x", number.Uint64()),true)
	//b, err := this.client.BlockByNumber(context.Background(), number)
	if err != nil {
		return nil, err
	}

	if rpcResp.Result != nil {
		var body *rpcBlock
		var head *BHeader

		data, _:= json.Marshal(rpcResp.Result)

		if err := json.Unmarshal(data, &head); err != nil {
			return nil, err
		}
		//fmt.Println(data)
		err = json.Unmarshal(data, &body)

		// Load uncles because they are not included in the block response.
		var uncles []*BHeader
		if len(body.UncleHashes) > 0 {
			uncles = make([]*BHeader, len(body.UncleHashes))
			reqs := make([]*jsonrpc.RPCRequest, len(body.UncleHashes))
			for i := range reqs {
				reqs[i] = &jsonrpc.RPCRequest{
					Method: "eth_getUncleByBlockHashAndIndex",
					Params:   []interface{}{body.Hash, hexutil.EncodeUint64(uint64(i))},
				}
			}
			uncleData, err := this.RpcClient.CallBatch(reqs)
			if  err != nil {
				return nil, err
			}
			fmt.Println(uncleData)
			fmt.Println(uncles)

			err = json.Unmarshal(data, &body)
			for i := range uncleData {
				uncleMarshal, _:= json.Marshal(uncleData[i].Result)

				err = json.Unmarshal(uncleMarshal, &uncles[i])
				uncles[i].Hash()
				if uncles[i] == nil {
					return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:])
				}
			}
		}
		// Fill the sender cache of transactions in the block.
		txs := make([]*types.Transaction, len(body.Transactions))
		for i, tx := range body.Transactions {
			if tx.From != nil {
				setSenderFromServer(tx.tx, *tx.From, body.Hash)
			}
			txs[i] = tx.tx
		}

		return NewBlockWithHeader(head).WithBody(txs, uncles), nil
	}

	return nil, nil
}

const ETH = 1_000_000_000_000_000_000
const DGC =    10_000_000_000_000_000
var (
	GenesisReword = math.MustParseBig256("3000000000000000000")	// 300DGC = 3ETH
	CarratReward  = math.MustParseBig256("3300000000000000000")	// 330DGC = 3.3ETH
	CarrathardforkheightMainnet = uint64(400000)
	CarrathardforkheightTestnet = uint64(641800)
)

// 당근을 위해 커스터마이징 된 부분
func getFixedRewardForDangnn(height uint64, mainnet bool) *big.Int {
	if mainnet == true {
		if height >= CarrathardforkheightMainnet {
			return new(big.Int).Set(CarratReward)
		}

	} else {
		if height >= CarrathardforkheightTestnet {
			return new(big.Int).Set(CarratReward)
		}
	}
	return new(big.Int).Set(GenesisReword)
}

func (this *Eth) GetRewardForUncle(height uint64, mainnet bool) *big.Int {
	reward := getFixedRewardForDangnn(height, mainnet)
	return new(big.Int).Div(reward, new(big.Int).SetInt64(32))
}

func (this *Eth) GetUncleReward(uHeight, height uint64, mainnet bool) *big.Int {
	reward := getFixedRewardForDangnn(height, mainnet)
	k := int64(height - uHeight)
	reward.Mul(big.NewInt(8-k), reward)
	reward.Div(reward, big.NewInt(8))
	return reward
}

func (this *Eth) GetReward(b *Block, rts types.Receipts) (blockReward *big.Int, txFee *big.Int, rewardForUncles *big.Int, err error) {
	correctHeight := b.Number().Uint64()
	fixedReward := getFixedRewardForDangnn(correctHeight, this.conifg.MainNet)
	blockReward = 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
	}

	uncleReward := this.GetRewardForUncle( correctHeight, this.conifg.MainNet)
	rewardForUncles = big.NewInt(0).Mul( uncleReward, big.NewInt(int64(uncleCount)))

	// fixed_Fee + tx_fee + uncle_include_fee
	blockReward.Add(blockReward, txFee)
	blockReward.Add(blockReward, rewardForUncles)

	return blockReward, txFee, rewardForUncles, 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) {
	b, err := this.GetTokenBalanceOf(common.HexToAddress(tokenAddress), 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, lastBlockNo uint64) (calls []model.Call, err error) {
	args := make([]interface{}, 0)
	args = append(args, txHash)
	args = append(args, map[string]interface {
	}{
		"tracer": "callTracer",
		"reexec": lastBlockNo - blockNo + 20,
	})

	resp, err := this.RpcClient.Call("debug_traceTransaction", args)
	if err != nil {
		log.Printf("debug_traceTransaction failed blockNo:%v txHas:%v\n", blockNo, txHash)
		return nil, err
	}

	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
}


//  totalSupply() :0x18160ddd
func (this *Eth) GetTokenTotalSupply(cntrAddr common.Address) (balance *big.Int, err error) {
	resp, err := this.RpcClient.Call("eth_call", map[string]interface{}{
		"data": "0x18160ddd",
		"to":   cntrAddr,
	},"latest")
	switch {
	case err != nil:
		return nil, err
	case resp.Error != nil:
		return nil, resp.Error
	case resp.Result == nil:
		return nil, errors.New("no result in JSON-RPC response")
	default:
		b, ret := new(big.Int).SetString(resp.Result.(string)[2:],16)
		if ret != true {
			return nil, errors.New("SetString: no result in JSON-RPC response")
		}
		return b, nil
	}
}


// "name()" : 0x06fdde03
func (this *Eth) GetTokenName(cntrAddr common.Address) ( string, error) {
	resp, err := this.RpcClient.Call("eth_call", map[string]interface{}{
		"data": "0x06fdde03",
		"to":   cntrAddr,
	},"latest")

	switch {
	case err != nil:
		return "", err
	case resp.Error != nil:
		return "", resp.Error
	case resp.Result == nil:
		return "", errors.New("no result in JSON-RPC response")
	default:
		bytes := hexutil.MustDecode(resp.Result.(string))
		result, err := this.AbiParsed.Unpack("name", bytes)
		if err != nil || result == nil {
			return "", err
		}
		return result[0].(string), err
	}
}


// Decimals() (0x313ce567)
func (this *Eth) GetTokenDecimals(cntrAddr common.Address) (uint8, error) {
	resp, err := this.RpcClient.Call("eth_call", map[string]interface{}{
		"data": "0x313ce567",
		"to":   cntrAddr,
	},"latest")

	switch {
	case err != nil:
		return 0, err
	case resp.Error != nil:
		return 0, resp.Error
	case resp.Result == nil:
		return 0, errors.New("no result in JSON-RPC response")
	default:
		b, ret := new(big.Int).SetString(resp.Result.(string)[2:],16)
		if ret != true {
			return 0, errors.New("SetString: no result in JSON-RPC response")
		}
		return uint8(b.Uint64()), nil
	}
}


// "95d89b41": "symbol()",
func (this *Eth) GetTokenSymbol(cntrAddr common.Address) (string, error) {
	resp, err := this.RpcClient.Call("eth_call", map[string]interface{}{
		"data": "0x95d89b41",
		"to":   cntrAddr,
	},"latest")

	switch {
	case err != nil:
		return "", err
	case resp.Error != nil:
		return "", resp.Error
	case resp.Result == nil:
		return "", errors.New("err")
	default:
		bytes := hexutil.MustDecode(resp.Result.(string))
		result, err := this.AbiParsed.Unpack("name", bytes)
		if err != nil || result == nil {
			return "", err
		}
		return result[0].(string), err
	}
}

func HexToUint64(u uint64) string {
	enc := strconv.FormatUint(u,16)
	lenth0 := 64 - len(enc)
	for i := 0; i < lenth0;i++ {
		enc = "0" + enc
	}
	return enc
}

// "c87b56dd": "tokenURI(uint256)",
func (this *Eth) GetNFTTokenURI(cntrAddr common.Address,tokenURI uint64) (string, error) {

	resp, err := this.RpcClient.Call("eth_call", map[string]interface{}{
		"data": "0xc87b56dd" +  HexToUint64(tokenURI),
		"to":   cntrAddr,
	},"latest")

	switch {
	case err != nil:
		return "", err
	case resp.Error != nil:
		return "", resp.Error
	case resp.Result == nil:
		return "", errors.New("err")
	default:
		bytes := hexutil.MustDecode(resp.Result.(string))
		result, err := this.AbiParsed.Unpack("tokenURI", bytes)
		if err != nil || result == nil {
			return "", err
		}
		return result[0].(string), err
	}
}

// "70a08231": "balanceOf(address)",
func (this *Eth) GetTokenBalanceOf(cntrAddr common.Address, ownerAddr common.Address) (balance *big.Int, err error) {
	resp, err := this.RpcClient.Call("eth_call", map[string]interface{}{
		"data": "0x70a08231000000000000000000000000" + ownerAddr.String()[2:],
		"to":   cntrAddr,
	},"latest")

	switch {
	case err != nil:
		return nil, err
	case resp.Error != nil:
		return nil, resp.Error
	case resp.Result == nil:
		return nil, errors.New("no result in JSON-RPC response")
	default:
		b, ret := new(big.Int).SetString(resp.Result.(string)[2:],16)
		if ret != true {
			return nil, errors.New("SetString: no result in JSON-RPC response")
		}
		return b, nil
	}
}