decred.org/dcrdex@v1.0.5/client/rpcserver/types.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 rpcserver
     5  
     6  import (
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"math"
    12  	"strconv"
    13  	"time"
    14  
    15  	"decred.org/dcrdex/client/core"
    16  	"decred.org/dcrdex/client/mm"
    17  	"decred.org/dcrdex/dex"
    18  	"decred.org/dcrdex/dex/config"
    19  	"decred.org/dcrdex/dex/encode"
    20  	"decred.org/dcrdex/dex/order"
    21  )
    22  
    23  // An orderID is a 256 bit number encoded as a hex string.
    24  const orderIdLen = 2 * order.OrderIDSize // 2 * 32
    25  
    26  var (
    27  	// errArgs is wrapped when arguments to the known command cannot be parsed.
    28  	errArgs = errors.New("unable to parse arguments")
    29  )
    30  
    31  // RawParams is used for all server requests.
    32  type RawParams struct {
    33  	PWArgs []encode.PassBytes `json:"PWArgs"`
    34  	Args   []string           `json:"args"`
    35  }
    36  
    37  // VersionResponse holds bisonw and bisonw rpc server version.
    38  type VersionResponse struct {
    39  	RPCServerVer *dex.Semver `json:"rpcServerVersion"`
    40  	BWVersion    *SemVersion `json:"dexcVersion"`
    41  }
    42  
    43  // SemVersion holds a semver version JSON object.
    44  type SemVersion struct {
    45  	VersionString string `json:"versionString"`
    46  	Major         uint32 `json:"major"`
    47  	Minor         uint32 `json:"minor"`
    48  	Patch         uint32 `json:"patch"`
    49  	Prerelease    string `json:"prerelease,omitempty"`
    50  	BuildMetadata string `json:"buildMetadata,omitempty"`
    51  }
    52  
    53  // getBondAssetsResponse is the getbondassets response payload.
    54  type getBondAssetsResponse struct {
    55  	Expiry uint64                     `json:"expiry"`
    56  	Assets map[string]*core.BondAsset `json:"assets"`
    57  }
    58  
    59  // tradeResponse is used when responding to the trade route.
    60  type tradeResponse struct {
    61  	OrderID string `json:"orderID"`
    62  	Sig     string `json:"sig"`
    63  	Stamp   uint64 `json:"stamp"`
    64  	Error   error  `json:"error,omitempty"`
    65  }
    66  
    67  // myOrdersResponse is used when responding to the myorders route.
    68  type myOrdersResponse []*myOrder
    69  
    70  // myOrder represents an order when responding to the myorders route.
    71  type myOrder struct {
    72  	Host        string   `json:"host"`
    73  	MarketName  string   `json:"marketName"`
    74  	BaseID      uint32   `json:"baseID"`
    75  	QuoteID     uint32   `json:"quoteID"`
    76  	ID          string   `json:"id"` // Can be empty if part of an InFlightOrder.
    77  	Type        string   `json:"type"`
    78  	Sell        bool     `json:"sell"`
    79  	Stamp       uint64   `json:"stamp"`
    80  	SubmitTime  uint64   `json:"submitTime"`
    81  	Age         string   `json:"age"`
    82  	Rate        uint64   `json:"rate,omitempty"`
    83  	Quantity    uint64   `json:"quantity"`
    84  	Filled      uint64   `json:"filled"`
    85  	Settled     uint64   `json:"settled"`
    86  	Status      string   `json:"status"`
    87  	Cancelling  bool     `json:"cancelling,omitempty"`
    88  	Canceled    bool     `json:"canceled,omitempty"`
    89  	TimeInForce string   `json:"tif,omitempty"`
    90  	Matches     []*match `json:"matches,omitempty"`
    91  }
    92  
    93  // match represents a match on an order. An order may have many matches.
    94  type match struct {
    95  	MatchID       string `json:"matchID"`
    96  	Status        string `json:"status"`
    97  	Revoked       bool   `json:"revoked"`
    98  	Rate          uint64 `json:"rate"`
    99  	Qty           uint64 `json:"qty"`
   100  	Side          string `json:"side"`
   101  	FeeRate       uint64 `json:"feeRate"`
   102  	Swap          string `json:"swap,omitempty"`
   103  	CounterSwap   string `json:"counterSwap,omitempty"`
   104  	Redeem        string `json:"redeem,omitempty"`
   105  	CounterRedeem string `json:"counterRedeem,omitempty"`
   106  	Refund        string `json:"refund,omitempty"`
   107  	Stamp         uint64 `json:"stamp"`
   108  	IsCancel      bool   `json:"isCancel"`
   109  }
   110  
   111  // discoverAcctForm is information necessary to discover an account used with a
   112  // certain dex.
   113  type discoverAcctForm struct {
   114  	addr    string
   115  	appPass encode.PassBytes
   116  	cert    any
   117  }
   118  
   119  // openWalletForm is information necessary to open a wallet.
   120  type openWalletForm struct {
   121  	assetID uint32
   122  	appPass encode.PassBytes
   123  }
   124  
   125  // walletStatusForm is information necessary to change a wallet's status.
   126  type walletStatusForm struct {
   127  	assetID uint32
   128  	disable bool
   129  }
   130  
   131  // newWalletForm is information necessary to create a new wallet.
   132  type newWalletForm struct {
   133  	assetID    uint32
   134  	walletType string
   135  	config     map[string]string
   136  	walletPass encode.PassBytes
   137  	appPass    encode.PassBytes
   138  }
   139  
   140  // helpForm is information necessary to obtain help.
   141  type helpForm struct {
   142  	helpWith         string
   143  	includePasswords bool
   144  }
   145  
   146  // tradeForm combines the application password and the user's trade details.
   147  type tradeForm struct {
   148  	appPass encode.PassBytes
   149  	srvForm *core.TradeForm
   150  }
   151  
   152  // multiTradeForm combines the application password and the user's trade
   153  // details.
   154  type multiTradeForm struct {
   155  	appPass encode.PassBytes
   156  	srvForm *core.MultiTradeForm
   157  }
   158  
   159  // cancelForm is information necessary to cancel a trade.
   160  type cancelForm struct {
   161  	orderID dex.Bytes
   162  }
   163  
   164  // sendOrWithdrawForm is information necessary to send or withdraw funds.
   165  type sendOrWithdrawForm struct {
   166  	appPass encode.PassBytes
   167  	assetID uint32
   168  	value   uint64
   169  	address string
   170  }
   171  
   172  // orderBookForm is information necessary to fetch an order book.
   173  type orderBookForm struct {
   174  	host    string
   175  	base    uint32
   176  	quote   uint32
   177  	nOrders uint64
   178  }
   179  
   180  // myOrdersForm is information necessary to fetch the user's orders.
   181  type myOrdersForm struct {
   182  	host  string
   183  	base  *uint32
   184  	quote *uint32
   185  }
   186  
   187  type deleteRecordsForm struct {
   188  	olderThan                     *time.Time
   189  	ordersFileStr, matchesFileStr string
   190  }
   191  
   192  // addRemovePeerForm is the information necessary to add or remove a wallet peer.
   193  type addRemovePeerForm struct {
   194  	assetID uint32
   195  	address string
   196  }
   197  
   198  type mmAvailableBalancesForm struct {
   199  	cfgFilePath string
   200  	mkt         *mm.MarketWithHost
   201  }
   202  
   203  type startBotForm struct {
   204  	appPass     encode.PassBytes
   205  	cfgFilePath string
   206  	mkt         *mm.MarketWithHost
   207  }
   208  
   209  type updateRunningBotForm struct {
   210  	cfgFilePath string
   211  	mkt         *mm.MarketWithHost
   212  	balances    *mm.BotInventoryDiffs
   213  }
   214  
   215  type updateRunningBotInventoryForm struct {
   216  	mkt      *mm.MarketWithHost
   217  	balances *mm.BotInventoryDiffs
   218  }
   219  
   220  type setVSPForm struct {
   221  	assetID uint32
   222  	addr    string
   223  }
   224  
   225  type purchaseTicketsForm struct {
   226  	assetID uint32
   227  	num     int
   228  	appPass encode.PassBytes
   229  }
   230  
   231  type setVotingPreferencesForm struct {
   232  	assetID                                   uint32
   233  	voteChoices, tSpendPolicy, treasuryPolicy map[string]string
   234  }
   235  
   236  type txHistoryForm struct {
   237  	assetID uint32
   238  	num     int
   239  	refID   *string
   240  	past    bool
   241  }
   242  
   243  // checkNArgs checks that args and pwArgs are the correct length.
   244  func checkNArgs(params *RawParams, nPWArgs, nArgs []int) error {
   245  	// For want, one integer indicates an exact match, two are the min and max.
   246  	check := func(have int, want []int) error {
   247  		if len(want) == 1 {
   248  			if want[0] != have {
   249  				return fmt.Errorf("%w: wanted %d argument but got %d argument", errArgs, want[0], have)
   250  			}
   251  		} else {
   252  			if have < want[0] || have > want[1] {
   253  				return fmt.Errorf("%w: wanted between %d and %d argument but got %d arguments", errArgs, want[0], want[1], have)
   254  			}
   255  		}
   256  		return nil
   257  	}
   258  	if err := check(len(params.Args), nArgs); err != nil {
   259  		return fmt.Errorf("arguments: %w", err)
   260  	}
   261  	if err := check(len(params.PWArgs), nPWArgs); err != nil {
   262  		return fmt.Errorf("password arguments: %w", err)
   263  	}
   264  	return nil
   265  }
   266  
   267  func checkIntArg(arg, name string, bitSize int) (int64, error) {
   268  	i, err := strconv.ParseInt(arg, 10, bitSize)
   269  	if err != nil {
   270  		return i, fmt.Errorf("%w: cannot parse %s: %v", errArgs, name, err)
   271  	}
   272  	return i, nil
   273  }
   274  
   275  func checkUIntArg(arg, name string, bitSize int) (uint64, error) {
   276  	i, err := strconv.ParseUint(arg, 10, bitSize)
   277  	if err != nil {
   278  		return i, fmt.Errorf("%w: cannot parse %s: %v", errArgs, name, err)
   279  	}
   280  	return i, nil
   281  }
   282  
   283  func checkBoolArg(arg, name string) (bool, error) {
   284  	b, err := strconv.ParseBool(arg)
   285  	if err != nil {
   286  		return b, fmt.Errorf("%w: %s must be a boolean: %v", errArgs, name, err)
   287  	}
   288  	return b, nil
   289  }
   290  
   291  func checkMapArg(arg, name string) (map[string]string, error) {
   292  	m := make(map[string]string)
   293  	err := json.Unmarshal([]byte(arg), &m)
   294  	if err != nil {
   295  		return nil, fmt.Errorf("%w: %s must be a JSON-encoded map[string]string: %v", errArgs, name, err)
   296  	}
   297  	return m, nil
   298  }
   299  
   300  func parseDiscoverAcctArgs(params *RawParams) (*discoverAcctForm, error) {
   301  	if err := checkNArgs(params, []int{1}, []int{1, 2}); err != nil {
   302  		return nil, err
   303  	}
   304  	var cert []byte
   305  	if len(params.Args) > 1 {
   306  		cert = []byte(params.Args[1])
   307  	}
   308  	req := &discoverAcctForm{
   309  		appPass: params.PWArgs[0],
   310  		addr:    params.Args[0],
   311  		cert:    cert,
   312  	}
   313  	return req, nil
   314  }
   315  
   316  func parseHelpArgs(params *RawParams) (*helpForm, error) {
   317  	if err := checkNArgs(params, []int{0}, []int{0, 2}); err != nil {
   318  		return nil, err
   319  	}
   320  	var helpWith string
   321  	if len(params.Args) > 0 {
   322  		helpWith = params.Args[0]
   323  	}
   324  	var includePasswords bool
   325  	if len(params.Args) > 1 {
   326  		var err error
   327  		includePasswords, err = checkBoolArg(params.Args[1], "includepasswords")
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  	}
   332  	return &helpForm{
   333  		helpWith:         helpWith,
   334  		includePasswords: includePasswords,
   335  	}, nil
   336  }
   337  
   338  func parseInitArgs(params *RawParams) (encode.PassBytes, *string, error) {
   339  	if err := checkNArgs(params, []int{1}, []int{0, 1}); err != nil {
   340  		return nil, nil, err
   341  	}
   342  	if len(params.PWArgs[0]) == 0 {
   343  		return nil, nil, fmt.Errorf("app password cannot be empty")
   344  	}
   345  	var seed *string
   346  	if len(params.Args) == 1 {
   347  		seed = &params.Args[0]
   348  	}
   349  	return params.PWArgs[0], seed, nil
   350  }
   351  
   352  func parseLoginArgs(params *RawParams) (encode.PassBytes, error) {
   353  	if err := checkNArgs(params, []int{1}, []int{0}); err != nil {
   354  		return nil, err
   355  	}
   356  	return params.PWArgs[0], nil
   357  }
   358  
   359  func parseNewWalletArgs(params *RawParams) (*newWalletForm, error) {
   360  	if err := checkNArgs(params, []int{2}, []int{2, 4}); err != nil {
   361  		return nil, err
   362  	}
   363  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	req := &newWalletForm{
   369  		appPass:    params.PWArgs[0],
   370  		walletType: params.Args[1],
   371  		walletPass: params.PWArgs[1],
   372  		assetID:    uint32(assetID),
   373  		config:     make(map[string]string),
   374  	}
   375  	if len(params.Args) > 2 {
   376  		req.config, err = config.Parse([]byte(params.Args[2]))
   377  		if err != nil {
   378  			return nil, fmt.Errorf("config parse error: %v", err)
   379  		}
   380  	}
   381  	if len(params.Args) > 3 {
   382  		cfg := make(map[string]string)
   383  		err := json.Unmarshal([]byte(params.Args[3]), &cfg)
   384  		if err != nil {
   385  			return nil, fmt.Errorf("JSON parse error: %v", err)
   386  		}
   387  		for key, val := range cfg {
   388  			if fileVal, found := req.config[key]; found {
   389  				log.Infof("Overriding config file setting %s=%s with %s", key, fileVal, val)
   390  			}
   391  			req.config[key] = val
   392  		}
   393  	}
   394  	return req, nil
   395  }
   396  
   397  func parseOpenWalletArgs(params *RawParams) (*openWalletForm, error) {
   398  	if err := checkNArgs(params, []int{1}, []int{1}); err != nil {
   399  		return nil, err
   400  	}
   401  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
   402  	if err != nil {
   403  		return nil, err
   404  	}
   405  	req := &openWalletForm{appPass: params.PWArgs[0], assetID: uint32(assetID)}
   406  	return req, nil
   407  }
   408  
   409  func parseCloseWalletArgs(params *RawParams) (uint32, error) {
   410  	if err := checkNArgs(params, []int{0}, []int{1}); err != nil {
   411  		return 0, err
   412  	}
   413  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
   414  	if err != nil {
   415  		return 0, err
   416  	}
   417  	return uint32(assetID), nil
   418  }
   419  
   420  func parseToggleWalletStatusArgs(params *RawParams) (*walletStatusForm, error) {
   421  	if err := checkNArgs(params, []int{0}, []int{2}); err != nil {
   422  		return nil, err
   423  	}
   424  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
   425  	if err != nil {
   426  		return nil, err
   427  	}
   428  	disable, err := checkBoolArg(params.Args[1], "disable")
   429  	if err != nil {
   430  		return nil, err
   431  	}
   432  	req := &walletStatusForm{assetID: uint32(assetID), disable: disable}
   433  	return req, nil
   434  }
   435  
   436  func parseGetDEXConfigArgs(params *RawParams) (host string, cert []byte, err error) {
   437  	if err := checkNArgs(params, []int{0}, []int{1, 2}); err != nil {
   438  		return "", nil, err
   439  	}
   440  	if len(params.Args) == 1 {
   441  		return params.Args[0], nil, nil
   442  	}
   443  	return params.Args[0], []byte(params.Args[1]), nil
   444  }
   445  
   446  func parseBondAssetsArgs(params *RawParams) (host string, cert []byte, err error) {
   447  	if err := checkNArgs(params, []int{0}, []int{1, 2}); err != nil {
   448  		return "", nil, err
   449  	}
   450  	if len(params.Args) == 1 {
   451  		return params.Args[0], nil, nil
   452  	}
   453  	return params.Args[0], []byte(params.Args[1]), nil
   454  }
   455  
   456  // bondopts 127.0.0.1:17273 2 2012345678 42
   457  func parseBondOptsArgs(params *RawParams) (*core.BondOptionsForm, error) {
   458  	if err := checkNArgs(params, []int{0}, []int{2, 5}); err != nil {
   459  		return nil, err
   460  	}
   461  
   462  	var targetTierP *uint64
   463  	targetTier, err := checkIntArg(params.Args[1], "targetTier", 17)
   464  	if err != nil {
   465  		return nil, err
   466  	}
   467  	if targetTier >= 0 {
   468  		targetTierU64 := uint64(targetTier)
   469  		targetTierP = &targetTierU64
   470  	}
   471  
   472  	var maxBondedP *uint64
   473  	if len(params.Args) > 2 {
   474  		maxBonded, err := checkIntArg(params.Args[2], "maxBonded", 64)
   475  		if err != nil {
   476  			return nil, err
   477  		}
   478  		if maxBonded >= 0 {
   479  			maxBondedU64 := uint64(maxBonded)
   480  			maxBondedP = &maxBondedU64
   481  		}
   482  	}
   483  
   484  	var bondAssetP *uint32
   485  	if len(params.Args) > 3 {
   486  		bondAsset, err := checkIntArg(params.Args[3], "bondAsset", 33)
   487  		if err != nil {
   488  			return nil, err
   489  		}
   490  		if bondAsset >= 0 {
   491  			bondAssetU32 := uint32(bondAsset)
   492  			bondAssetP = &bondAssetU32
   493  		}
   494  	}
   495  
   496  	var penaltyComps *uint16
   497  	if len(params.Args) > 4 {
   498  		pc, err := checkIntArg(params.Args[4], "penaltyComps", 16)
   499  		if err != nil {
   500  			return nil, err
   501  		}
   502  		if pc > math.MaxUint16 {
   503  			return nil, fmt.Errorf("penaltyComps out of range (0, %d)", math.MaxUint16)
   504  		}
   505  		if pc > 0 {
   506  			pc16 := uint16(pc)
   507  			penaltyComps = &pc16
   508  		}
   509  	}
   510  
   511  	req := &core.BondOptionsForm{
   512  		Host:         params.Args[0],
   513  		TargetTier:   targetTierP,
   514  		MaxBondedAmt: maxBondedP,
   515  		BondAssetID:  bondAssetP,
   516  		PenaltyComps: penaltyComps,
   517  	}
   518  	return req, nil
   519  }
   520  
   521  func parsePostBondArgs(params *RawParams) (*core.PostBondForm, error) {
   522  	if err := checkNArgs(params, []int{1}, []int{3, 5}); err != nil {
   523  		return nil, err
   524  	}
   525  	bond, err := checkUIntArg(params.Args[1], "bond", 64)
   526  	if err != nil {
   527  		return nil, err
   528  	}
   529  	asset, err := checkUIntArg(params.Args[2], "asset", 32)
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  
   534  	var maintain *bool
   535  	if len(params.Args) > 3 {
   536  		tt, err := checkBoolArg(params.Args[3], "maintain")
   537  		if err != nil {
   538  			return nil, err
   539  		}
   540  		maintain = &tt
   541  	}
   542  
   543  	var cert []byte
   544  	if len(params.Args) > 4 {
   545  		cert = []byte(params.Args[4])
   546  	}
   547  
   548  	asset32 := uint32(asset)
   549  	req := &core.PostBondForm{
   550  		AppPass:      params.PWArgs[0],
   551  		Addr:         params.Args[0],
   552  		Cert:         cert,
   553  		Bond:         bond,
   554  		Asset:        &asset32,
   555  		MaintainTier: maintain,
   556  	}
   557  	return req, nil
   558  }
   559  
   560  func parseTradeArgs(params *RawParams) (*tradeForm, error) {
   561  	if err := checkNArgs(params, []int{1}, []int{9}); err != nil {
   562  		return nil, err
   563  	}
   564  	isLimit, err := checkBoolArg(params.Args[1], "isLimit")
   565  	if err != nil {
   566  		return nil, err
   567  	}
   568  	sell, err := checkBoolArg(params.Args[2], "sell")
   569  	if err != nil {
   570  		return nil, err
   571  	}
   572  	base, err := checkUIntArg(params.Args[3], "base", 32)
   573  	if err != nil {
   574  		return nil, err
   575  	}
   576  	quote, err := checkUIntArg(params.Args[4], "quote", 32)
   577  	if err != nil {
   578  		return nil, err
   579  	}
   580  	qty, err := checkUIntArg(params.Args[5], "qty", 64)
   581  	if err != nil {
   582  		return nil, err
   583  	}
   584  	rate, err := checkUIntArg(params.Args[6], "rate", 64)
   585  	if err != nil {
   586  		return nil, err
   587  	}
   588  	tifnow, err := checkBoolArg(params.Args[7], "immediate")
   589  	if err != nil {
   590  		return nil, err
   591  	}
   592  	options, err := checkMapArg(params.Args[8], "options")
   593  	if err != nil {
   594  		return nil, err
   595  	}
   596  	req := &tradeForm{
   597  		appPass: params.PWArgs[0],
   598  		srvForm: &core.TradeForm{
   599  			Host:    params.Args[0],
   600  			IsLimit: isLimit,
   601  			Sell:    sell,
   602  			Base:    uint32(base),
   603  			Quote:   uint32(quote),
   604  			Qty:     qty,
   605  			Rate:    rate,
   606  			TifNow:  tifnow,
   607  			Options: options,
   608  		},
   609  	}
   610  	return req, nil
   611  }
   612  
   613  func parseMultiTradeArgs(params *RawParams) (*multiTradeForm, error) {
   614  	if err := checkNArgs(params, []int{1}, []int{6, 7}); err != nil {
   615  		return nil, err
   616  	}
   617  
   618  	sell, err := checkBoolArg(params.Args[1], "sell")
   619  	if err != nil {
   620  		return nil, err
   621  	}
   622  
   623  	base, err := checkUIntArg(params.Args[2], "base", 32)
   624  	if err != nil {
   625  		return nil, err
   626  	}
   627  
   628  	quote, err := checkUIntArg(params.Args[3], "quote", 32)
   629  	if err != nil {
   630  		return nil, err
   631  	}
   632  
   633  	maxLock, err := checkUIntArg(params.Args[4], "maxLock", 64)
   634  	if err != nil {
   635  		return nil, err
   636  	}
   637  
   638  	var p [][2]uint64
   639  	if err := json.Unmarshal([]byte(params.Args[5]), &p); err != nil {
   640  		return nil, fmt.Errorf("failed to unmarshal placements: %v", err)
   641  	}
   642  	placements := make([]*core.QtyRate, 0, len(p))
   643  	for _, placement := range p {
   644  		placements = append(placements, &core.QtyRate{
   645  			Qty:  placement[0],
   646  			Rate: placement[1],
   647  		})
   648  	}
   649  
   650  	options := make(map[string]string)
   651  	if len(params.Args) == 7 {
   652  		options, err = checkMapArg(params.Args[6], "options")
   653  		if err != nil {
   654  			return nil, err
   655  		}
   656  	}
   657  
   658  	return &multiTradeForm{
   659  		appPass: params.PWArgs[0],
   660  		srvForm: &core.MultiTradeForm{
   661  			Host:       params.Args[0],
   662  			Sell:       sell,
   663  			Base:       uint32(base),
   664  			Quote:      uint32(quote),
   665  			Placements: placements,
   666  			Options:    options,
   667  			MaxLock:    maxLock,
   668  		},
   669  	}, nil
   670  }
   671  
   672  func parseCancelArgs(params *RawParams) (*cancelForm, error) {
   673  	if err := checkNArgs(params, []int{0}, []int{1}); err != nil {
   674  		return nil, err
   675  	}
   676  	id := params.Args[0]
   677  	if len(id) != orderIdLen {
   678  		return nil, fmt.Errorf("%w: orderID has incorrect length", errArgs)
   679  	}
   680  	oidB, err := hex.DecodeString(id)
   681  	if err != nil {
   682  		return nil, fmt.Errorf("%w: invalid order id hex", errArgs)
   683  	}
   684  	return &cancelForm{orderID: oidB}, nil
   685  }
   686  
   687  func parseSendOrWithdrawArgs(params *RawParams) (*sendOrWithdrawForm, error) {
   688  	if err := checkNArgs(params, []int{1}, []int{3}); err != nil {
   689  		return nil, err
   690  	}
   691  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
   692  	if err != nil {
   693  		return nil, err
   694  	}
   695  	value, err := checkUIntArg(params.Args[1], "value", 64)
   696  	if err != nil {
   697  		return nil, err
   698  	}
   699  	req := &sendOrWithdrawForm{
   700  		appPass: params.PWArgs[0],
   701  		assetID: uint32(assetID),
   702  		value:   value,
   703  		address: params.Args[2],
   704  	}
   705  	return req, nil
   706  }
   707  
   708  func parseBchWithdrawArgs(params *RawParams) (appPW encode.PassBytes, recipient string, _ error) {
   709  	if err := checkNArgs(params, []int{1}, []int{1}); err != nil {
   710  		return nil, "", err
   711  	}
   712  	return params.PWArgs[0], params.Args[0], nil
   713  }
   714  
   715  func parseRescanWalletArgs(params *RawParams) (uint32, bool, error) {
   716  	if err := checkNArgs(params, []int{0}, []int{1, 2}); err != nil {
   717  		return 0, false, err
   718  	}
   719  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
   720  	if err != nil {
   721  		return 0, false, err
   722  	}
   723  	var force bool // do not rescan with active orders by default
   724  	if len(params.Args) > 1 {
   725  		force, err = checkBoolArg(params.Args[1], "force")
   726  		if err != nil {
   727  			return 0, false, err
   728  		}
   729  	}
   730  	return uint32(assetID), force, nil
   731  }
   732  
   733  func parseOrderBookArgs(params *RawParams) (*orderBookForm, error) {
   734  	if err := checkNArgs(params, []int{0}, []int{3, 4}); err != nil {
   735  		return nil, err
   736  	}
   737  	base, err := checkUIntArg(params.Args[1], "base", 32)
   738  	if err != nil {
   739  		return nil, err
   740  	}
   741  	quote, err := checkUIntArg(params.Args[2], "quote", 32)
   742  	if err != nil {
   743  		return nil, err
   744  	}
   745  	var nOrders uint64
   746  	if len(params.Args) > 3 {
   747  		nOrders, err = checkUIntArg(params.Args[3], "nOrders", 64)
   748  		if err != nil {
   749  			return nil, err
   750  		}
   751  	}
   752  	req := &orderBookForm{
   753  		host:    params.Args[0],
   754  		base:    uint32(base),
   755  		quote:   uint32(quote),
   756  		nOrders: nOrders,
   757  	}
   758  	return req, nil
   759  }
   760  
   761  func parseMyOrdersArgs(params *RawParams) (*myOrdersForm, error) {
   762  	if err := checkNArgs(params, []int{0}, []int{0, 3}); err != nil {
   763  		return nil, err
   764  	}
   765  	req := new(myOrdersForm)
   766  	switch len(params.Args) {
   767  	case 3:
   768  		// Args 1 and 2 should be base ID and quote ID. If present,
   769  		// they are a pair.
   770  		quote, err := checkUIntArg(params.Args[2], "quote", 32)
   771  		if err != nil {
   772  			return nil, err
   773  		}
   774  		q := uint32(quote)
   775  		req.quote = &q
   776  
   777  		base, err := checkUIntArg(params.Args[1], "base", 32)
   778  		if err != nil {
   779  			return nil, err
   780  		}
   781  		b := uint32(base)
   782  		req.base = &b
   783  		fallthrough
   784  	case 1:
   785  		req.host = params.Args[0]
   786  	case 2:
   787  		// Received a base ID but no quote ID.
   788  		return nil, fmt.Errorf("%w: no market quote ID", errArgs)
   789  	}
   790  	return req, nil
   791  }
   792  
   793  func parseAppSeedArgs(params *RawParams) (encode.PassBytes, error) {
   794  	if err := checkNArgs(params, []int{1}, []int{0}); err != nil {
   795  		return nil, err
   796  	}
   797  	return params.PWArgs[0], nil
   798  }
   799  
   800  func parseDeleteArchivedRecordsArgs(params *RawParams) (form *deleteRecordsForm, err error) {
   801  	if err = checkNArgs(params, []int{0}, []int{0, 3}); err != nil {
   802  		return nil, err
   803  	}
   804  	form = new(deleteRecordsForm)
   805  	switch len(params.Args) {
   806  	case 3:
   807  		form.ordersFileStr = params.Args[2]
   808  		fallthrough
   809  	case 2:
   810  		form.matchesFileStr = params.Args[1]
   811  		fallthrough
   812  	case 1:
   813  		olderThanStr := params.Args[0]
   814  		if olderThanStr == "" || olderThanStr == "0" {
   815  			break
   816  		}
   817  		olderThanMs, err := strconv.ParseInt(olderThanStr, 10, 64)
   818  		if err != nil {
   819  			return nil, fmt.Errorf("invalid older than time %q: %v", olderThanStr, err)
   820  		}
   821  		t := time.UnixMilli(olderThanMs)
   822  		form.olderThan = &t
   823  	}
   824  	return form, nil
   825  }
   826  
   827  func parseWalletPeersArgs(params *RawParams) (uint32, error) {
   828  	if err := checkNArgs(params, []int{0}, []int{1}); err != nil {
   829  		return 0, err
   830  	}
   831  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
   832  	if err != nil {
   833  		return 0, err
   834  	}
   835  	return uint32(assetID), nil
   836  }
   837  
   838  func parseAddRemoveWalletPeerArgs(params *RawParams) (form *addRemovePeerForm, err error) {
   839  	if err = checkNArgs(params, []int{0}, []int{2}); err != nil {
   840  		return nil, err
   841  	}
   842  	form = new(addRemovePeerForm)
   843  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
   844  	if err != nil {
   845  		return nil, fmt.Errorf("invalid assetID: %v", err)
   846  	}
   847  	form.assetID = uint32(assetID)
   848  	form.address = params.Args[1]
   849  	return form, nil
   850  }
   851  
   852  func parseNotificationsArgs(params *RawParams) (int, error) {
   853  	if err := checkNArgs(params, []int{0}, []int{1}); err != nil {
   854  		return 0, err
   855  	}
   856  	num, err := checkUIntArg(params.Args[0], "num", 32)
   857  	if err != nil {
   858  		return 0, fmt.Errorf("invalid num: %v", err)
   859  	}
   860  	return int(num), nil
   861  }
   862  
   863  func parseMktWithHost(host, baseID, quoteID string) (*mm.MarketWithHost, error) {
   864  	mkt := new(mm.MarketWithHost)
   865  	mkt.Host = host
   866  	base, err := checkUIntArg(baseID, "baseID", 32)
   867  	if err != nil {
   868  		return nil, fmt.Errorf("invalid baseID: %v", err)
   869  	}
   870  	mkt.BaseID = uint32(base)
   871  	quote, err := checkUIntArg(quoteID, "quoteID", 32)
   872  	if err != nil {
   873  		return nil, fmt.Errorf("invalid quoteID: %v", err)
   874  	}
   875  	mkt.QuoteID = uint32(quote)
   876  	return mkt, nil
   877  }
   878  
   879  func parseMMAvailableBalancesArgs(params *RawParams) (*mmAvailableBalancesForm, error) {
   880  	if err := checkNArgs(params, []int{0}, []int{4}); err != nil {
   881  		return nil, err
   882  	}
   883  	form := new(mmAvailableBalancesForm)
   884  	form.cfgFilePath = params.Args[0]
   885  	mkt, err := parseMktWithHost(params.Args[1], params.Args[2], params.Args[3])
   886  	if err != nil {
   887  		return nil, err
   888  	}
   889  	form.mkt = mkt
   890  	return form, nil
   891  }
   892  
   893  func parseBotDiffs(balanceArg string) (map[uint32]int64, error) {
   894  	balances := make([][2]int64, 0)
   895  	err := json.Unmarshal([]byte(balanceArg), &balances)
   896  	if err != nil {
   897  		return nil, fmt.Errorf("failed to unmarshal bot balances: %v", err)
   898  	}
   899  
   900  	toReturn := make(map[uint32]int64)
   901  	for _, b := range balances {
   902  		toReturn[uint32(b[0])] = b[1]
   903  	}
   904  
   905  	return toReturn, nil
   906  }
   907  
   908  func parseBotBalances(balanceArg string) (map[uint32]uint64, error) {
   909  	diffs, err := parseBotDiffs(balanceArg)
   910  	if err != nil {
   911  		return nil, err
   912  	}
   913  
   914  	toReturn := make(map[uint32]uint64)
   915  	for k, v := range diffs {
   916  		if v < 0 {
   917  			return nil, fmt.Errorf("balances must be positive")
   918  		}
   919  		toReturn[k] = uint64(v)
   920  	}
   921  
   922  	return toReturn, nil
   923  }
   924  
   925  func parseStartBotArgs(params *RawParams) (*startBotForm, error) {
   926  	if err := checkNArgs(params, []int{1}, []int{6}); err != nil {
   927  		return nil, err
   928  	}
   929  	form := new(startBotForm)
   930  	form.appPass = params.PWArgs[0]
   931  	form.cfgFilePath = params.Args[0]
   932  
   933  	mkt, err := parseMktWithHost(params.Args[1], params.Args[2], params.Args[3])
   934  	if err != nil {
   935  		return nil, err
   936  	}
   937  	form.mkt = mkt
   938  
   939  	return form, nil
   940  }
   941  
   942  func parseStopBotArgs(params *RawParams) (*mm.MarketWithHost, error) {
   943  	if err := checkNArgs(params, []int{0}, []int{3}); err != nil {
   944  		return nil, err
   945  	}
   946  	return parseMktWithHost(params.Args[0], params.Args[1], params.Args[2])
   947  }
   948  
   949  func parseUpdateRunningBotArgs(params *RawParams) (*updateRunningBotForm, error) {
   950  	if err := checkNArgs(params, []int{0}, []int{4, 6}); err != nil {
   951  		return nil, err
   952  	}
   953  
   954  	form := new(updateRunningBotForm)
   955  
   956  	form.cfgFilePath = params.Args[0]
   957  
   958  	mkt, err := parseMktWithHost(params.Args[1], params.Args[2], params.Args[3])
   959  	if err != nil {
   960  		return nil, err
   961  	}
   962  	form.mkt = mkt
   963  
   964  	dexBals := make(map[uint32]int64)
   965  	cexBals := make(map[uint32]int64)
   966  
   967  	if len(params.Args) > 4 {
   968  		dexBals, err = parseBotDiffs(params.Args[4])
   969  		if err != nil {
   970  			return nil, fmt.Errorf("failed to unmarshal dex balance diffs: %v", err)
   971  		}
   972  	}
   973  
   974  	if len(params.Args) > 5 {
   975  		cexBals, err = parseBotDiffs(params.Args[5])
   976  		if err != nil {
   977  			return nil, fmt.Errorf("failed to unmarshal cex balance diffs: %v", err)
   978  		}
   979  	}
   980  
   981  	form.balances = &mm.BotInventoryDiffs{
   982  		DEX: dexBals,
   983  		CEX: cexBals,
   984  	}
   985  
   986  	return form, nil
   987  }
   988  
   989  func parseUpdateRunningBotInventoryArgs(params *RawParams) (*updateRunningBotInventoryForm, error) {
   990  	if err := checkNArgs(params, []int{0}, []int{5}); err != nil {
   991  		return nil, err
   992  	}
   993  
   994  	form := new(updateRunningBotInventoryForm)
   995  
   996  	mkt, err := parseMktWithHost(params.Args[0], params.Args[1], params.Args[2])
   997  	if err != nil {
   998  		return nil, err
   999  	}
  1000  	form.mkt = mkt
  1001  
  1002  	dexBals, err := parseBotDiffs(params.Args[3])
  1003  	if err != nil {
  1004  		return nil, fmt.Errorf("failed to unmarshal dex balance diffs: %v", err)
  1005  	}
  1006  
  1007  	cexBals, err := parseBotDiffs(params.Args[4])
  1008  	if err != nil {
  1009  		return nil, fmt.Errorf("failed to unmarshal cex balance diffs: %v", err)
  1010  	}
  1011  
  1012  	form.balances = &mm.BotInventoryDiffs{
  1013  		DEX: dexBals,
  1014  		CEX: cexBals,
  1015  	}
  1016  
  1017  	return form, nil
  1018  }
  1019  
  1020  func parseSetVSPArgs(params *RawParams) (*setVSPForm, error) {
  1021  	if err := checkNArgs(params, []int{0}, []int{2}); err != nil {
  1022  		return nil, err
  1023  	}
  1024  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
  1025  	if err != nil {
  1026  		return nil, fmt.Errorf("invalid assetID: %v", err)
  1027  	}
  1028  	return &setVSPForm{
  1029  		assetID: uint32(assetID),
  1030  		addr:    params.Args[1],
  1031  	}, nil
  1032  }
  1033  
  1034  func parsePurchaseTicketsArgs(params *RawParams) (*purchaseTicketsForm, error) {
  1035  	if err := checkNArgs(params, []int{1}, []int{2}); err != nil {
  1036  		return nil, err
  1037  	}
  1038  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
  1039  	if err != nil {
  1040  		return nil, fmt.Errorf("invalid assetID: %v", err)
  1041  	}
  1042  	num, err := checkUIntArg(params.Args[1], "num", 32)
  1043  	if err != nil {
  1044  		return nil, fmt.Errorf("invalid num: %v", err)
  1045  	}
  1046  	return &purchaseTicketsForm{
  1047  		assetID: uint32(assetID),
  1048  		num:     int(num),
  1049  		appPass: params.PWArgs[0],
  1050  	}, nil
  1051  }
  1052  
  1053  func parseStakeStatusArgs(params *RawParams) (uint32, error) {
  1054  	if err := checkNArgs(params, []int{0}, []int{1}); err != nil {
  1055  		return 0, err
  1056  	}
  1057  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
  1058  	if err != nil {
  1059  		return 0, fmt.Errorf("invalid assetID: %v", err)
  1060  	}
  1061  	return uint32(assetID), nil
  1062  }
  1063  
  1064  func parseSetVotingPreferencesArgs(params *RawParams) (*setVotingPreferencesForm, error) {
  1065  	err := checkNArgs(params, []int{0}, []int{1, 4})
  1066  	if err != nil {
  1067  		return nil, err
  1068  	}
  1069  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
  1070  	if err != nil {
  1071  		return nil, fmt.Errorf("invalid assetID: %v", err)
  1072  	}
  1073  	var p string
  1074  	form := &setVotingPreferencesForm{
  1075  		assetID: uint32(assetID),
  1076  	}
  1077  	switch len(params.Args) {
  1078  	case 4:
  1079  		p = params.Args[3]
  1080  		if p != "" {
  1081  			form.treasuryPolicy, err = checkMapArg(p, "treasury policy")
  1082  			if err != nil {
  1083  				return nil, err
  1084  			}
  1085  		}
  1086  		fallthrough
  1087  	case 3:
  1088  		p = params.Args[2]
  1089  		if p != "" {
  1090  			form.tSpendPolicy, err = checkMapArg(p, "tspend policy")
  1091  			if err != nil {
  1092  				return nil, err
  1093  			}
  1094  		}
  1095  		fallthrough
  1096  	case 2:
  1097  		p = params.Args[1]
  1098  		if p != "" {
  1099  			form.voteChoices, err = checkMapArg(p, "vote choices")
  1100  			if err != nil {
  1101  				return nil, err
  1102  			}
  1103  		}
  1104  	}
  1105  	return form, nil
  1106  }
  1107  
  1108  func parseTxHistoryArgs(params *RawParams) (*txHistoryForm, error) {
  1109  	err := checkNArgs(params, []int{0}, []int{1, 4})
  1110  	if err != nil {
  1111  		return nil, err
  1112  	}
  1113  
  1114  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
  1115  	if err != nil {
  1116  		return nil, fmt.Errorf("invalid assetID: %v", err)
  1117  	}
  1118  
  1119  	var num int64
  1120  	if len(params.Args) > 1 {
  1121  		num, err = checkIntArg(params.Args[1], "num", 64)
  1122  		if err != nil {
  1123  			return nil, fmt.Errorf("invalid num: %v", err)
  1124  		}
  1125  	}
  1126  
  1127  	var refID *string
  1128  	var past bool
  1129  	if len(params.Args) > 2 {
  1130  		if len(params.Args) != 4 {
  1131  			return nil, fmt.Errorf("refID provided without past")
  1132  		}
  1133  
  1134  		refID = &params.Args[2]
  1135  
  1136  		past, err = checkBoolArg(params.Args[3], "past")
  1137  		if err != nil {
  1138  			return nil, err
  1139  		}
  1140  	}
  1141  
  1142  	return &txHistoryForm{
  1143  		assetID: uint32(assetID),
  1144  		num:     int(num),
  1145  		refID:   refID,
  1146  		past:    past,
  1147  	}, nil
  1148  }
  1149  
  1150  type walletTxForm struct {
  1151  	assetID uint32
  1152  	txID    string
  1153  }
  1154  
  1155  func parseWalletTxArgs(params *RawParams) (*walletTxForm, error) {
  1156  	err := checkNArgs(params, []int{0}, []int{2})
  1157  	if err != nil {
  1158  		return nil, err
  1159  	}
  1160  
  1161  	assetID, err := checkUIntArg(params.Args[0], "assetID", 32)
  1162  	if err != nil {
  1163  		return nil, fmt.Errorf("invalid assetID: %v", err)
  1164  	}
  1165  
  1166  	return &walletTxForm{
  1167  		assetID: uint32(assetID),
  1168  		txID:    params.Args[1],
  1169  	}, nil
  1170  }