decred.org/dcrdex@v1.0.5/client/asset/btc/electrum/jsonrpc.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package electrum
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"reflect"
    10  	"strconv"
    11  )
    12  
    13  type positional []any
    14  
    15  type request struct {
    16  	Jsonrpc string          `json:"jsonrpc"`
    17  	ID      uint64          `json:"id"`
    18  	Method  string          `json:"method"`
    19  	Params  json.RawMessage `json:"params"` // [] for positional args or {} for named args, no bare types
    20  }
    21  
    22  // RPCError represents a JSON-RPC error object.
    23  type RPCError struct {
    24  	Code    int    `json:"code,omitempty"`
    25  	Message string `json:"message,omitempty"`
    26  }
    27  
    28  // Error satisfies the error interface.
    29  func (e RPCError) Error() string {
    30  	return fmt.Sprintf("code %d: %q", e.Code, e.Message)
    31  }
    32  
    33  type response struct {
    34  	// The "jsonrpc" fields is ignored.
    35  	ID     uint64          `json:"id"`     // response to request
    36  	Method string          `json:"method"` // notification for subscription
    37  	Result json.RawMessage `json:"result"`
    38  	Error  *RPCError       `json:"error"`
    39  }
    40  
    41  type ntfn = request // weird but true
    42  type ntfnData struct {
    43  	Params json.RawMessage `json:"params"`
    44  }
    45  
    46  func prepareRequest(id uint64, method string, args any) ([]byte, error) {
    47  	// nil args should marshal as [] instead of null.
    48  	if args == nil {
    49  		args = []json.RawMessage{}
    50  	}
    51  	// else {
    52  	// 	switch reflect.TypeOf(args).Kind() {
    53  	// 	case reflect.Interface, reflect.Pointer, reflect.Slice, reflect.Map:
    54  	// 		if reflect.ValueOf(args).IsNil() {
    55  	// 			args = []json.RawMessage{}
    56  	// 		}
    57  	// 	default:
    58  	// 	}
    59  	// }
    60  
    61  	switch rt := reflect.TypeOf(args); rt.Kind() {
    62  	case reflect.Struct, reflect.Slice:
    63  	case reflect.Ptr: // allow pointer to struct
    64  		if rt.Elem().Kind() != reflect.Struct {
    65  			return nil, fmt.Errorf("invalid arg type %v, must be slice or struct", rt)
    66  		}
    67  	default:
    68  		return nil, fmt.Errorf("invalid arg type %v, must be slice or struct", rt)
    69  	}
    70  
    71  	params, err := json.Marshal(args)
    72  	if err != nil {
    73  		return nil, fmt.Errorf("failed to marshal arguments: %v", err)
    74  	}
    75  	req := &request{
    76  		Jsonrpc: "2.0", // electrum wallet seems to respond with 2.0 regardless
    77  		ID:      id,
    78  		Method:  method,
    79  		Params:  params,
    80  	}
    81  	return json.Marshal(req)
    82  }
    83  
    84  // floatString is for unmarshalling a string with a float like "123.34" directly
    85  // into a float64 instead of a string and then converting later.
    86  type floatString float64
    87  
    88  func (fs *floatString) UnmarshalJSON(b []byte) error {
    89  	// Try to strip the string contents out of quotes.
    90  	var str string
    91  	if err := json.Unmarshal(b, &str); err != nil {
    92  		return err // wasn't a string
    93  	}
    94  	fl, err := strconv.ParseFloat(str, 64)
    95  	if err != nil {
    96  		return err // The string didn't contain a float.
    97  	}
    98  	*fs = floatString(fl)
    99  	return nil
   100  }