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 } }