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  }