github.com/gagliardetto/solana-go@v1.11.0/rpc/jsonrpc/jsonrpc.go (about) 1 // Package jsonrpc provides a JSON-RPC 2.0 client that sends JSON-RPC requests and receives JSON-RPC responses using HTTP. 2 package jsonrpc 3 4 import ( 5 "bytes" 6 "context" 7 stdjson "encoding/json" 8 "errors" 9 "fmt" 10 "net/http" 11 "reflect" 12 "sync/atomic" 13 14 "github.com/davecgh/go-spew/spew" 15 "github.com/google/uuid" 16 jsoniter "github.com/json-iterator/go" 17 ) 18 19 var json = jsoniter.ConfigCompatibleWithStandardLibrary 20 21 const ( 22 jsonrpcVersion = "2.0" 23 ) 24 25 // RPCClient sends JSON-RPC requests over HTTP to the provided JSON-RPC backend. 26 // 27 // RPCClient is created using the factory function NewClient(). 28 type RPCClient interface { 29 // Call is used to send a JSON-RPC request to the server endpoint. 30 // 31 // The spec states, that params can only be an array or an object, no primitive values. 32 // So there are a few simple rules to notice: 33 // 34 // 1. no params: params field is omitted. e.g. Call("getinfo") 35 // 36 // 2. single params primitive value: value is wrapped in array. e.g. Call("getByID", 1423) 37 // 38 // 3. single params value array or object: value is unchanged. e.g. Call("storePerson", &Person{Name: "Alex"}) 39 // 40 // 4. multiple params values: always wrapped in array. e.g. Call("setDetails", "Alex, 35, "Germany", true) 41 // 42 // Examples: 43 // Call("getinfo") -> {"method": "getinfo"} 44 // Call("getPersonId", 123) -> {"method": "getPersonId", "params": [123]} 45 // Call("setName", "Alex") -> {"method": "setName", "params": ["Alex"]} 46 // Call("setMale", true) -> {"method": "setMale", "params": [true]} 47 // Call("setNumbers", []int{1, 2, 3}) -> {"method": "setNumbers", "params": [1, 2, 3]} 48 // Call("setNumbers", 1, 2, 3) -> {"method": "setNumbers", "params": [1, 2, 3]} 49 // Call("savePerson", &Person{Name: "Alex", Age: 35}) -> {"method": "savePerson", "params": {"name": "Alex", "age": 35}} 50 // Call("setPersonDetails", "Alex", 35, "Germany") -> {"method": "setPersonDetails", "params": ["Alex", 35, "Germany"}} 51 // 52 // for more information, see the examples or the unit tests 53 Call(ctx context.Context, method string, params ...interface{}) (*RPCResponse, error) 54 55 // CallRaw is like Call() but without magic in the requests.Params field. 56 // The RPCRequest object is sent exactly as you provide it. 57 // See docs: NewRequest, RPCRequest, Params() 58 // 59 // It is recommended to first consider Call() and CallFor() 60 CallRaw(ctx context.Context, request *RPCRequest) (*RPCResponse, error) 61 62 // CallFor is a very handy function to send a JSON-RPC request to the server endpoint 63 // and directly specify an object to store the response. 64 // 65 // out: will store the unmarshaled object, if request was successful. 66 // should always be provided by references. can be nil even on success. 67 // the behaviour is the same as expected from json.Unmarshal() 68 // 69 // method and params: see Call() function 70 // 71 // if the request was not successful (network, http error) or the rpc response returns an error, 72 // an error is returned. if it was an JSON-RPC error it can be casted 73 // to *RPCError. 74 // 75 CallFor(ctx context.Context, out interface{}, method string, params ...interface{}) error 76 77 // CallBatch invokes a list of RPCRequests in a single batch request. 78 // 79 // Most convenient is to use the following form: 80 // CallBatch(RPCRequests{ 81 // Batch("myMethod1", 1, 2, 3), 82 // Batch("myMethod2), "Test"), 83 // }) 84 // 85 // You can create the []*RPCRequest array yourself, but it is not recommended and you should notice the following: 86 // - field Params is sent as provided, so Params: 2 forms an invalid json (correct would be Params: []int{2}) 87 // - you can use the helper function Params(1, 2, 3) to use the same format as in Call() 88 // - field JSONRPC is overwritten and set to value: "2.0" 89 // - field ID is overwritten and set incrementally and maps to the array position (e.g. requests[5].ID == 5) 90 // 91 // 92 // Returns RPCResponses that is of type []*RPCResponse 93 // - note that a list of RPCResponses can be received unordered so it can happen that: responses[i] != responses[i].ID 94 // - RPCPersponses is enriched with helper functions e.g.: responses.HasError() returns true if one of the responses holds an RPCError 95 CallBatch(ctx context.Context, requests RPCRequests) (RPCResponses, error) 96 97 // CallBatchRaw invokes a list of RPCRequests in a single batch request. 98 // It sends the RPCRequests parameter is it passed (no magic, no id autoincrement). 99 // 100 // Consider to use CallBatch() instead except you have some good reason not to. 101 // 102 // CallBatchRaw(RPCRequests{ 103 // &RPCRequest{ 104 // ID: 123, // this won't be replaced in CallBatchRaw 105 // JSONRPC: "wrong", // this won't be replaced in CallBatchRaw 106 // Method: "myMethod1", 107 // Params: []int{1}, // there is no magic, be sure to only use array or object 108 // }, 109 // &RPCRequest{ 110 // ID: 612, 111 // JSONRPC: "2.0", 112 // Method: "myMethod2", 113 // Params: Params("Alex", 35, true), // you can use helper function Params() (see doc) 114 // }, 115 // }) 116 // 117 // Returns RPCResponses that is of type []*RPCResponse 118 // - note that a list of RPCResponses can be received unordered 119 // - the id's must be mapped against the id's you provided 120 // - RPCPersponses is enriched with helper functions e.g.: responses.HasError() returns true if one of the responses holds an RPCError 121 CallBatchRaw(ctx context.Context, requests RPCRequests) (RPCResponses, error) 122 123 CallForInto(ctx context.Context, out interface{}, method string, params []interface{}) error 124 CallWithCallback(ctx context.Context, method string, params []interface{}, callback func(*http.Request, *http.Response) error) error 125 Close() error 126 } 127 128 // RPCRequest represents a JSON-RPC request object. 129 // 130 // Method: string containing the method to be invoked 131 // 132 // Params: can be nil. if not must be an json array or object 133 // 134 // ID: may always set to 1 for single requests. Should be unique for every request in one batch request. 135 // 136 // JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0 137 // 138 // See: http://www.jsonrpc.org/specification#request_object 139 // 140 // Most of the time you shouldn't create the RPCRequest object yourself. 141 // The following functions do that for you: 142 // Call(), CallFor(), NewRequest() 143 // 144 // If you want to create it yourself (e.g. in batch or CallRaw()), consider using Params(). 145 // Params() is a helper function that uses the same parameter syntax as Call(). 146 // 147 // e.g. to manually create an RPCRequest object: 148 // 149 // request := &RPCRequest{ 150 // Method: "myMethod", 151 // Params: Params("Alex", 35, true), 152 // } 153 // 154 // If you know what you are doing you can omit the Params() call to avoid some reflection but potentially create incorrect rpc requests: 155 // 156 // request := &RPCRequest{ 157 // Method: "myMethod", 158 // Params: 2, <-- invalid since a single primitive value must be wrapped in an array --> no magic without Params() 159 // } 160 // 161 // correct: 162 // 163 // request := &RPCRequest{ 164 // Method: "myMethod", 165 // Params: []int{2}, <-- invalid since a single primitive value must be wrapped in an array 166 // } 167 type RPCRequest struct { 168 Method string `json:"method"` 169 Params interface{} `json:"params,omitempty"` 170 ID any `json:"id"` 171 JSONRPC string `json:"jsonrpc"` 172 } 173 174 // NewRequest returns a new RPCRequest that can be created using the same convenient parameter syntax as Call() 175 // 176 // e.g. NewRequest("myMethod", "Alex", 35, true) 177 func NewRequest(method string, params ...interface{}) *RPCRequest { 178 request := &RPCRequest{ 179 Method: method, 180 Params: Params(params...), 181 JSONRPC: jsonrpcVersion, 182 ID: newID(), 183 } 184 return request 185 } 186 187 // RPCResponse represents a JSON-RPC response object. 188 // 189 // Result: holds the result of the rpc call if no error occurred, nil otherwise. can be nil even on success. 190 // 191 // Error: holds an RPCError object if an error occurred. must be nil on success. 192 // 193 // ID: may always be 0 for single requests. is unique for each request in a batch call (see CallBatch()) 194 // 195 // JSONRPC: must always be set to "2.0" for JSON-RPC version 2.0 196 // 197 // See: http://www.jsonrpc.org/specification#response_object 198 type RPCResponse struct { 199 JSONRPC string `json:"jsonrpc"` 200 Result stdjson.RawMessage `json:"result,omitempty"` 201 Error *RPCError `json:"error,omitempty"` 202 ID any `json:"id"` 203 } 204 205 // RPCError represents a JSON-RPC error object if an RPC error occurred. 206 // 207 // Code: holds the error code 208 // 209 // Message: holds a short error message 210 // 211 // Data: holds additional error data, may be nil 212 // 213 // See: http://www.jsonrpc.org/specification#error_object 214 type RPCError struct { 215 Code int `json:"code"` 216 Message string `json:"message"` 217 Data interface{} `json:"data,omitempty"` 218 } 219 220 var spewConf = spew.ConfigState{ 221 Indent: " ", 222 DisableMethods: true, 223 DisablePointerMethods: true, 224 SortKeys: true, 225 } 226 227 // Error function is provided to be used as error object. 228 func (e *RPCError) Error() string { 229 return spewConf.Sdump(e) 230 } 231 232 // HTTPError represents a error that occurred on HTTP level. 233 // 234 // An error of type HTTPError is returned when a HTTP error occurred (status code) 235 // and the body could not be parsed to a valid RPCResponse object that holds a RPCError. 236 // 237 // Otherwise a RPCResponse object is returned with a RPCError field that is not nil. 238 type HTTPError struct { 239 Code int 240 err error 241 } 242 243 // HTTPClient is an abstraction for a HTTP client 244 type HTTPClient interface { 245 Do(*http.Request) (*http.Response, error) 246 CloseIdleConnections() 247 } 248 249 func NewHTTPError(code int, err error) *HTTPError { 250 return &HTTPError{ 251 Code: code, 252 err: err, 253 } 254 } 255 256 // Error function is provided to be used as error object. 257 func (e *HTTPError) Error() string { 258 return e.err.Error() 259 } 260 261 type rpcClient struct { 262 endpoint string 263 httpClient HTTPClient 264 customHeaders map[string]string 265 } 266 267 // RPCClientOpts can be provided to NewClientWithOpts() to change configuration of RPCClient. 268 // 269 // HTTPClient: provide a custom http.Client (e.g. to set a proxy, or tls options) 270 // 271 // CustomHeaders: provide custom headers, e.g. to set BasicAuth 272 type RPCClientOpts struct { 273 HTTPClient HTTPClient 274 CustomHeaders map[string]string 275 } 276 277 // RPCResponses is of type []*RPCResponse. 278 // This type is used to provide helper functions on the result list 279 type RPCResponses []*RPCResponse 280 281 // AsMap returns the responses as map with response id as key. 282 func (res RPCResponses) AsMap() map[any]*RPCResponse { 283 resMap := make(map[any]*RPCResponse, 0) 284 for _, r := range res { 285 actualID := r.ID 286 if actualID != nil { 287 if asFloat, ok := actualID.(stdjson.Number); ok { 288 asInt64, err := asFloat.Int64() 289 if err == nil { 290 actualID = int(asInt64) 291 } 292 } else { 293 resMap[actualID] = r 294 } 295 } 296 resMap[actualID] = r 297 } 298 return resMap 299 } 300 301 // GetByID returns the response object of the given id, nil if it does not exist. 302 func (res RPCResponses) GetByID(id any) *RPCResponse { 303 for _, r := range res { 304 if r.ID == id { 305 return r 306 } 307 } 308 return nil 309 } 310 311 // HasError returns true if one of the response objects has Error field != nil 312 func (res RPCResponses) HasError() bool { 313 for _, res := range res { 314 if res.Error != nil { 315 return true 316 } 317 } 318 return false 319 } 320 321 // RPCRequests is of type []*RPCRequest. 322 // This type is used to provide helper functions on the request list 323 type RPCRequests []*RPCRequest 324 325 // NewClient returns a new RPCClient instance with default configuration. 326 // 327 // endpoint: JSON-RPC service URL to which JSON-RPC requests are sent. 328 func NewClient(endpoint string) RPCClient { 329 return NewClientWithOpts(endpoint, nil) 330 } 331 332 // NewClientWithOpts returns a new RPCClient instance with custom configuration. 333 // 334 // endpoint: JSON-RPC service URL to which JSON-RPC requests are sent. 335 // 336 // opts: RPCClientOpts provide custom configuration 337 func NewClientWithOpts(endpoint string, opts *RPCClientOpts) RPCClient { 338 rpcClient := &rpcClient{ 339 endpoint: endpoint, 340 httpClient: &http.Client{}, 341 customHeaders: make(map[string]string), 342 } 343 344 if opts == nil { 345 return rpcClient 346 } 347 348 if opts.HTTPClient != nil { 349 rpcClient.httpClient = opts.HTTPClient 350 } 351 352 if opts.CustomHeaders != nil { 353 for k, v := range opts.CustomHeaders { 354 rpcClient.customHeaders[k] = v 355 } 356 } 357 358 return rpcClient 359 } 360 361 func (client *rpcClient) Call(ctx context.Context, method string, params ...interface{}) (*RPCResponse, error) { 362 request := &RPCRequest{ 363 Method: method, 364 Params: Params(params...), 365 JSONRPC: jsonrpcVersion, 366 } 367 368 return client.doCall(ctx, request) 369 } 370 371 func (client *rpcClient) Close() error { 372 if client.httpClient != nil { 373 client.httpClient.CloseIdleConnections() 374 } 375 return nil 376 } 377 378 func (client *rpcClient) CallForInto( 379 ctx context.Context, 380 out interface{}, 381 method string, 382 params []interface{}, 383 ) error { 384 request := &RPCRequest{ 385 Method: method, 386 JSONRPC: jsonrpcVersion, 387 } 388 389 if params != nil { 390 request.Params = params 391 } 392 393 rpcResponse, err := client.doCall(ctx, request) 394 if err != nil { 395 return err 396 } 397 398 if rpcResponse.Error != nil { 399 return rpcResponse.Error 400 } 401 402 return rpcResponse.GetObject(out) 403 } 404 405 func (client *rpcClient) CallWithCallback( 406 ctx context.Context, 407 method string, 408 params []interface{}, 409 callback func(*http.Request, *http.Response) error, 410 ) error { 411 request := &RPCRequest{ 412 Method: method, 413 JSONRPC: jsonrpcVersion, 414 } 415 416 if params != nil { 417 request.Params = params 418 } 419 420 return client.doCallWithCallbackOnHTTPResponse( 421 ctx, 422 request, 423 callback, 424 ) 425 } 426 427 func (client *rpcClient) CallRaw(ctx context.Context, request *RPCRequest) (*RPCResponse, error) { 428 return client.doCall(ctx, request) 429 } 430 431 func (client *rpcClient) CallFor(ctx context.Context, out interface{}, method string, params ...interface{}) error { 432 rpcResponse, err := client.Call(ctx, method, params...) 433 if err != nil { 434 return err 435 } 436 437 if rpcResponse.Error != nil { 438 return rpcResponse.Error 439 } 440 441 return rpcResponse.GetObject(out) 442 } 443 444 func (client *rpcClient) CallBatch(ctx context.Context, requests RPCRequests) (RPCResponses, error) { 445 if len(requests) == 0 { 446 return nil, errors.New("empty request list") 447 } 448 449 for i, req := range requests { 450 req.ID = i 451 req.JSONRPC = jsonrpcVersion 452 } 453 454 return client.doBatchCall(ctx, requests) 455 } 456 457 func (client *rpcClient) CallBatchRaw(ctx context.Context, requests RPCRequests) (RPCResponses, error) { 458 if len(requests) == 0 { 459 return nil, errors.New("empty request list") 460 } 461 462 return client.doBatchCall(ctx, requests) 463 } 464 465 func (client *rpcClient) newRequest(ctx context.Context, req interface{}) (*http.Request, error) { 466 body, err := json.Marshal(req) 467 if err != nil { 468 return nil, err 469 } 470 471 request, err := http.NewRequestWithContext(ctx, "POST", client.endpoint, bytes.NewReader(body)) 472 if err != nil { 473 return request, err 474 } 475 476 request.Header.Set("Content-Type", "application/json") 477 request.Header.Set("Accept", "application/json") 478 479 // set default headers first, so that even content type and accept can be overwritten 480 for k, v := range client.customHeaders { 481 request.Header.Set(k, v) 482 } 483 484 return request, nil 485 } 486 487 func (client *rpcClient) doCall( 488 ctx context.Context, 489 RPCRequest *RPCRequest, 490 ) (*RPCResponse, error) { 491 var rpcResponse *RPCResponse 492 err := client.doCallWithCallbackOnHTTPResponse( 493 ctx, 494 RPCRequest, 495 func(httpRequest *http.Request, httpResponse *http.Response) error { 496 decoder := json.NewDecoder(httpResponse.Body) 497 decoder.DisallowUnknownFields() 498 decoder.UseNumber() 499 err := decoder.Decode(&rpcResponse) 500 // parsing error 501 if err != nil { 502 // if we have some http error, return it 503 if httpResponse.StatusCode >= 400 { 504 return &HTTPError{ 505 Code: httpResponse.StatusCode, 506 err: fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %w", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err), 507 } 508 } 509 return fmt.Errorf("rpc call %v() on %v status code: %v. could not decode body to rpc response: %w", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode, err) 510 } 511 512 // response body empty 513 if rpcResponse == nil { 514 // if we have some http error, return it 515 if httpResponse.StatusCode >= 400 { 516 return &HTTPError{ 517 Code: httpResponse.StatusCode, 518 err: fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode), 519 } 520 } 521 return fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", RPCRequest.Method, httpRequest.URL.String(), httpResponse.StatusCode) 522 } 523 return nil 524 }, 525 ) 526 if err != nil { 527 return nil, err 528 } 529 530 return rpcResponse, nil 531 } 532 533 var UseIntegerID = false 534 535 var integerID = new(atomic.Uint64) 536 537 var useFixedID = false 538 539 const defaultFixedID = 1 540 541 func newID() any { 542 if useFixedID { 543 return defaultFixedID 544 } 545 if UseIntegerID { 546 return integerID.Add(1) 547 } 548 return uuid.New().String() 549 } 550 551 func (client *rpcClient) doCallWithCallbackOnHTTPResponse( 552 ctx context.Context, 553 RPCRequest *RPCRequest, 554 callback func(*http.Request, *http.Response) error, 555 ) error { 556 if RPCRequest != nil && RPCRequest.ID == nil { 557 RPCRequest.ID = newID() 558 } 559 httpRequest, err := client.newRequest(ctx, RPCRequest) 560 if err != nil { 561 if httpRequest != nil { 562 return fmt.Errorf("rpc call %v() on %v: %w", RPCRequest.Method, httpRequest.URL.String(), err) 563 } 564 return fmt.Errorf("rpc call %v(): %w", RPCRequest.Method, err) 565 } 566 httpResponse, err := client.httpClient.Do(httpRequest) 567 if err != nil { 568 return fmt.Errorf("rpc call %v() on %v: %w", RPCRequest.Method, httpRequest.URL.String(), err) 569 } 570 defer httpResponse.Body.Close() 571 572 return callback(httpRequest, httpResponse) 573 } 574 575 func (client *rpcClient) doBatchCall(ctx context.Context, rpcRequest []*RPCRequest) ([]*RPCResponse, error) { 576 httpRequest, err := client.newRequest(ctx, rpcRequest) 577 if err != nil { 578 if httpRequest != nil { 579 return nil, fmt.Errorf("rpc batch call on %v: %w", httpRequest.URL.String(), err) 580 } 581 return nil, fmt.Errorf("rpc batch call: %w", err) 582 } 583 httpResponse, err := client.httpClient.Do(httpRequest) 584 if err != nil { 585 return nil, fmt.Errorf("rpc batch call on %v: %w", httpRequest.URL.String(), err) 586 } 587 defer httpResponse.Body.Close() 588 589 var rpcResponse RPCResponses 590 decoder := json.NewDecoder(httpResponse.Body) 591 decoder.DisallowUnknownFields() 592 decoder.UseNumber() 593 err = decoder.Decode(&rpcResponse) 594 // parsing error 595 if err != nil { 596 // if we have some http error, return it 597 if httpResponse.StatusCode >= 400 { 598 return nil, &HTTPError{ 599 Code: httpResponse.StatusCode, 600 err: fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %w", httpRequest.URL.String(), httpResponse.StatusCode, err), 601 } 602 } 603 return nil, fmt.Errorf("rpc batch call on %v status code: %v. could not decode body to rpc response: %w", httpRequest.URL.String(), httpResponse.StatusCode, err) 604 } 605 606 // response body empty 607 if rpcResponse == nil || len(rpcResponse) == 0 { 608 // if we have some http error, return it 609 if httpResponse.StatusCode >= 400 { 610 return nil, &HTTPError{ 611 Code: httpResponse.StatusCode, 612 err: fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode), 613 } 614 } 615 return nil, fmt.Errorf("rpc batch call on %v status code: %v. rpc response missing", httpRequest.URL.String(), httpResponse.StatusCode) 616 } 617 618 return rpcResponse, nil 619 } 620 621 // Params is a helper function that uses the same parameter syntax as Call(). 622 // But you should consider to always use NewRequest() instead. 623 // 624 // e.g. to manually create an RPCRequest object: 625 // 626 // request := &RPCRequest{ 627 // Method: "myMethod", 628 // Params: Params("Alex", 35, true), 629 // } 630 // 631 // same with new request: 632 // request := NewRequest("myMethod", "Alex", 35, true) 633 // 634 // If you know what you are doing you can omit the Params() call but potentially create incorrect rpc requests: 635 // 636 // request := &RPCRequest{ 637 // Method: "myMethod", 638 // Params: 2, <-- invalid since a single primitive value must be wrapped in an array --> no magic without Params() 639 // } 640 // 641 // correct: 642 // 643 // request := &RPCRequest{ 644 // Method: "myMethod", 645 // Params: []int{2}, <-- invalid since a single primitive value must be wrapped in an array 646 // } 647 func Params(params ...interface{}) interface{} { 648 var finalParams interface{} 649 650 // if params was nil skip this and p stays nil 651 if params != nil { 652 switch len(params) { 653 case 0: // no parameters were provided, do nothing so finalParam is nil and will be omitted 654 case 1: // one param was provided, use it directly as is, or wrap primitive types in array 655 if params[0] != nil { 656 var typeOf reflect.Type 657 658 // traverse until nil or not a pointer type 659 for typeOf = reflect.TypeOf(params[0]); typeOf != nil && typeOf.Kind() == reflect.Ptr; typeOf = typeOf.Elem() { 660 } 661 662 if typeOf != nil { 663 // now check if we can directly marshal the type or if it must be wrapped in an array 664 switch typeOf.Kind() { 665 // for these types we just do nothing, since value of p is already unwrapped from the array params 666 case reflect.Struct: 667 finalParams = params[0] 668 case reflect.Array: 669 finalParams = params[0] 670 case reflect.Slice: 671 finalParams = params[0] 672 case reflect.Interface: 673 finalParams = params[0] 674 case reflect.Map: 675 finalParams = params[0] 676 default: // everything else must stay in an array (int, string, etc) 677 finalParams = params 678 } 679 } 680 } else { 681 finalParams = params 682 } 683 default: // if more than one parameter was provided it should be treated as an array 684 finalParams = params 685 } 686 } 687 688 return finalParams 689 } 690 691 // GetObject converts the rpc response to an arbitrary type. 692 // 693 // The function works as you would expect it from json.Unmarshal() 694 func (RPCResponse *RPCResponse) GetObject(toType interface{}) error { 695 if RPCResponse == nil { 696 return errors.New("rpc response is nil") 697 } 698 rv := reflect.ValueOf(toType) 699 if rv.Kind() != reflect.Ptr { 700 return fmt.Errorf("expected a pointer, got a value: %s", reflect.TypeOf(toType)) 701 } 702 if RPCResponse.Result == nil { 703 RPCResponse.Result = []byte(`null`) 704 } 705 return json.Unmarshal(RPCResponse.Result, toType) 706 }