decred.org/dcrdex@v1.0.5/client/rpcserver/handlers.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/json" 8 "fmt" 9 "os" 10 "sort" 11 "strings" 12 "time" 13 14 "decred.org/dcrdex/client/asset" 15 "decred.org/dcrdex/client/core" 16 "decred.org/dcrdex/client/mm" 17 "decred.org/dcrdex/dex" 18 "decred.org/dcrdex/dex/msgjson" 19 "decred.org/dcrdex/dex/order" 20 ) 21 22 // routes 23 const ( 24 cancelRoute = "cancel" 25 closeWalletRoute = "closewallet" 26 discoverAcctRoute = "discoveracct" 27 exchangesRoute = "exchanges" 28 helpRoute = "help" 29 initRoute = "init" 30 loginRoute = "login" 31 logoutRoute = "logout" 32 myOrdersRoute = "myorders" 33 newWalletRoute = "newwallet" 34 openWalletRoute = "openwallet" 35 toggleWalletStatusRoute = "togglewalletstatus" 36 orderBookRoute = "orderbook" 37 getDEXConfRoute = "getdexconfig" 38 bondAssetsRoute = "bondassets" 39 postBondRoute = "postbond" 40 bondOptionsRoute = "bondopts" 41 tradeRoute = "trade" 42 versionRoute = "version" 43 walletsRoute = "wallets" 44 rescanWalletRoute = "rescanwallet" 45 withdrawRoute = "withdraw" 46 sendRoute = "send" 47 appSeedRoute = "appseed" 48 deleteArchivedRecordsRoute = "deletearchivedrecords" 49 walletPeersRoute = "walletpeers" 50 addWalletPeerRoute = "addwalletpeer" 51 removeWalletPeerRoute = "removewalletpeer" 52 notificationsRoute = "notifications" 53 startBotRoute = "startmmbot" 54 stopBotRoute = "stopmmbot" 55 updateRunningBotCfgRoute = "updaterunningbotcfg" 56 updateRunningBotInvRoute = "updaterunningbotinv" 57 mmAvailableBalancesRoute = "mmavailablebalances" 58 mmStatusRoute = "mmstatus" 59 multiTradeRoute = "multitrade" 60 stakeStatusRoute = "stakestatus" 61 setVSPRoute = "setvsp" 62 purchaseTicketsRoute = "purchasetickets" 63 setVotingPreferencesRoute = "setvotingprefs" 64 txHistoryRoute = "txhistory" 65 walletTxRoute = "wallettx" 66 withdrawBchSpvRoute = "withdrawbchspv" 67 ) 68 69 const ( 70 initializedStr = "app initialized" 71 walletCreatedStr = "%s wallet created and unlocked" 72 walletLockedStr = "%s wallet locked" 73 walletUnlockedStr = "%s wallet unlocked" 74 canceledOrderStr = "canceled order %s" 75 logoutStr = "goodbye" 76 walletStatusStr = "%s wallet has been %s" 77 setVotePrefsStr = "vote preferences set" 78 setVSPStr = "vsp set to %s" 79 ) 80 81 // createResponse creates a msgjson response payload. 82 func createResponse(op string, res any, resErr *msgjson.Error) *msgjson.ResponsePayload { 83 encodedRes, err := json.Marshal(res) 84 if err != nil { 85 err := fmt.Errorf("unable to marshal data for %s: %w", op, err) 86 panic(err) 87 } 88 return &msgjson.ResponsePayload{Result: encodedRes, Error: resErr} 89 } 90 91 // usage creates and returns usage for route combined with a passed error as a 92 // *msgjson.ResponsePayload. 93 func usage(route string, err error) *msgjson.ResponsePayload { 94 usage, _ := commandUsage(route, false) 95 resErr := msgjson.NewError(msgjson.RPCArgumentsError, "%v\n\n%s", err, usage) 96 return createResponse(route, nil, resErr) 97 } 98 99 // routes maps routes to a handler function. 100 var routes = map[string]func(s *RPCServer, params *RawParams) *msgjson.ResponsePayload{ 101 cancelRoute: handleCancel, 102 closeWalletRoute: handleCloseWallet, 103 discoverAcctRoute: handleDiscoverAcct, 104 exchangesRoute: handleExchanges, 105 helpRoute: handleHelp, 106 initRoute: handleInit, 107 loginRoute: handleLogin, 108 logoutRoute: handleLogout, 109 myOrdersRoute: handleMyOrders, 110 newWalletRoute: handleNewWallet, 111 openWalletRoute: handleOpenWallet, 112 toggleWalletStatusRoute: handleToggleWalletStatus, 113 orderBookRoute: handleOrderBook, 114 getDEXConfRoute: handleGetDEXConfig, 115 postBondRoute: handlePostBond, 116 bondOptionsRoute: handleBondOptions, 117 bondAssetsRoute: handleBondAssets, 118 tradeRoute: handleTrade, 119 versionRoute: handleVersion, 120 walletsRoute: handleWallets, 121 rescanWalletRoute: handleRescanWallet, 122 withdrawRoute: handleWithdraw, 123 sendRoute: handleSend, 124 appSeedRoute: handleAppSeed, 125 deleteArchivedRecordsRoute: handleDeleteArchivedRecords, 126 walletPeersRoute: handleWalletPeers, 127 addWalletPeerRoute: handleAddWalletPeer, 128 removeWalletPeerRoute: handleRemoveWalletPeer, 129 notificationsRoute: handleNotifications, 130 startBotRoute: handleStartBot, 131 stopBotRoute: handleStopBot, 132 mmAvailableBalancesRoute: handleMMAvailableBalances, 133 mmStatusRoute: handleMMStatus, 134 updateRunningBotCfgRoute: handleUpdateRunningBotCfg, 135 updateRunningBotInvRoute: handleUpdateRunningBotInventory, 136 multiTradeRoute: handleMultiTrade, 137 stakeStatusRoute: handleStakeStatus, 138 setVSPRoute: handleSetVSP, 139 purchaseTicketsRoute: handlePurchaseTickets, 140 setVotingPreferencesRoute: handleSetVotingPreferences, 141 txHistoryRoute: handleTxHistory, 142 walletTxRoute: handleWalletTx, 143 withdrawBchSpvRoute: handleWithdrawBchSpv, 144 } 145 146 // handleHelp handles requests for help. Returns general help for all commands 147 // if no arguments are passed or verbose help if the passed argument is a known 148 // command. 149 func handleHelp(_ *RPCServer, params *RawParams) *msgjson.ResponsePayload { 150 form, err := parseHelpArgs(params) 151 if err != nil { 152 return usage(helpRoute, err) 153 } 154 res := "" 155 if form.helpWith == "" { 156 // List all commands if no arguments. 157 res = ListCommands(form.includePasswords) 158 } else { 159 var err error 160 res, err = commandUsage(form.helpWith, form.includePasswords) 161 if err != nil { 162 resErr := msgjson.NewError(msgjson.RPCUnknownRoute, "error getting usage: %v", err) 163 return createResponse(helpRoute, nil, resErr) 164 } 165 } 166 return createResponse(helpRoute, &res, nil) 167 } 168 169 // handleInit handles requests for init. *msgjson.ResponsePayload.Error is empty 170 // if successful. 171 func handleInit(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 172 appPass, seed, err := parseInitArgs(params) 173 if err != nil { 174 return usage(initRoute, err) 175 } 176 defer appPass.Clear() 177 if _, err := s.core.InitializeClient(appPass, seed); err != nil { 178 resErr := msgjson.NewError(msgjson.RPCInitError, "unable to initialize client: %v", err) 179 return createResponse(initRoute, nil, resErr) 180 } 181 res := initializedStr 182 return createResponse(initRoute, &res, nil) 183 } 184 185 // handleVersion handles requests for version. It returns the rpc server version 186 // and dexc version. 187 func handleVersion(s *RPCServer, _ *RawParams) *msgjson.ResponsePayload { 188 result := &VersionResponse{ 189 RPCServerVer: &dex.Semver{ 190 Major: rpcSemverMajor, 191 Minor: rpcSemverMinor, 192 Patch: rpcSemverPatch, 193 }, 194 BWVersion: s.bwVersion, 195 } 196 197 return createResponse(versionRoute, result, nil) 198 } 199 200 // handleNewWallet handles requests for newwallet. 201 // *msgjson.ResponsePayload.Error is empty if successful. Returns a 202 // msgjson.RPCWalletExistsError if a wallet for the assetID already exists. 203 // Wallet will be unlocked if successful. 204 func handleNewWallet(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 205 form, err := parseNewWalletArgs(params) 206 if err != nil { 207 return usage(newWalletRoute, err) 208 } 209 210 // zero password params in request payload when done handling this request 211 defer func() { 212 form.appPass.Clear() 213 form.walletPass.Clear() 214 }() 215 216 if s.core.WalletState(form.assetID) != nil { 217 resErr := msgjson.NewError(msgjson.RPCWalletExistsError, "error creating %s wallet: wallet already exists", dex.BipIDSymbol(form.assetID)) 218 return createResponse(newWalletRoute, nil, resErr) 219 } 220 221 walletDef, err := asset.WalletDef(form.assetID, form.walletType) 222 if err != nil { 223 resErr := msgjson.NewError(msgjson.RPCWalletDefinitionError, "error creating %s wallet: unable to get wallet definition: %v", dex.BipIDSymbol(form.assetID), err) 224 return createResponse(newWalletRoute, nil, resErr) 225 } 226 227 // Apply default config options if they exist. 228 for _, opt := range walletDef.ConfigOpts { 229 if _, has := form.config[opt.Key]; !has { 230 form.config[opt.Key] = opt.DefaultValue 231 } 232 } 233 234 // Wallet does not exist yet. Try to create it. 235 err = s.core.CreateWallet(form.appPass, form.walletPass, &core.WalletForm{ 236 Type: form.walletType, 237 AssetID: form.assetID, 238 Config: form.config, 239 }) 240 if err != nil { 241 resErr := msgjson.NewError(msgjson.RPCCreateWalletError, "error creating %s wallet: %v", dex.BipIDSymbol(form.assetID), err) 242 return createResponse(newWalletRoute, nil, resErr) 243 } 244 245 err = s.core.OpenWallet(form.assetID, form.appPass) 246 if err != nil { 247 resErr := msgjson.NewError(msgjson.RPCOpenWalletError, "wallet connected, but failed to open with provided password: %v", err) 248 return createResponse(newWalletRoute, nil, resErr) 249 } 250 251 res := fmt.Sprintf(walletCreatedStr, dex.BipIDSymbol(form.assetID)) 252 return createResponse(newWalletRoute, &res, nil) 253 } 254 255 // handleOpenWallet handles requests for openWallet. 256 // *msgjson.ResponsePayload.Error is empty if successful. Requires the app 257 // password. Opens the wallet. 258 func handleOpenWallet(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 259 form, err := parseOpenWalletArgs(params) 260 if err != nil { 261 return usage(openWalletRoute, err) 262 } 263 defer form.appPass.Clear() 264 265 err = s.core.OpenWallet(form.assetID, form.appPass) 266 if err != nil { 267 resErr := msgjson.NewError(msgjson.RPCOpenWalletError, "error unlocking %s wallet: %v", dex.BipIDSymbol(form.assetID), err) 268 return createResponse(openWalletRoute, nil, resErr) 269 } 270 271 res := fmt.Sprintf(walletUnlockedStr, dex.BipIDSymbol(form.assetID)) 272 return createResponse(openWalletRoute, &res, nil) 273 } 274 275 // handleCloseWallet handles requests for closeWallet. 276 // *msgjson.ResponsePayload.Error is empty if successful. Closes the wallet. 277 func handleCloseWallet(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 278 assetID, err := parseCloseWalletArgs(params) 279 if err != nil { 280 return usage(closeWalletRoute, err) 281 } 282 if err := s.core.CloseWallet(assetID); err != nil { 283 resErr := msgjson.NewError(msgjson.RPCCloseWalletError, "unable to close wallet %s: %v", dex.BipIDSymbol(assetID), err) 284 return createResponse(closeWalletRoute, nil, resErr) 285 } 286 287 res := fmt.Sprintf(walletLockedStr, dex.BipIDSymbol(assetID)) 288 return createResponse(closeWalletRoute, &res, nil) 289 } 290 291 // handleToggleWalletStatus handles requests for toggleWalletStatus. 292 // *msgjson.ResponsePayload.Error is empty if successful. Disables or enables a 293 // wallet. 294 func handleToggleWalletStatus(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 295 form, err := parseToggleWalletStatusArgs(params) 296 if err != nil { 297 return usage(toggleWalletStatusRoute, err) 298 } 299 if err := s.core.ToggleWalletStatus(form.assetID, form.disable); err != nil { 300 resErr := msgjson.NewError(msgjson.RPCToggleWalletStatusError, "unable to change %s wallet status: %v", dex.BipIDSymbol(form.assetID), err) 301 return createResponse(toggleWalletStatusRoute, nil, resErr) 302 } 303 304 status := "enabled" 305 if form.disable { 306 status = "disabled" 307 } 308 309 res := fmt.Sprintf(walletStatusStr, dex.BipIDSymbol(form.assetID), status) 310 return createResponse(toggleWalletStatusRoute, &res, nil) 311 } 312 313 // handleWallets handles requests for wallets. Returns a list of wallet details. 314 func handleWallets(s *RPCServer, _ *RawParams) *msgjson.ResponsePayload { 315 walletsStates := s.core.Wallets() 316 return createResponse(walletsRoute, walletsStates, nil) 317 } 318 319 // handleBondAssets handles requests for bondassets. 320 // *msgjson.ResponsePayload.Error is empty if successful. Requires the address 321 // of a dex and returns the bond expiry and supported asset bond details. 322 func handleBondAssets(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 323 host, cert, err := parseBondAssetsArgs(params) 324 if err != nil { 325 return usage(bondAssetsRoute, err) 326 } 327 exchInf := s.core.Exchanges() 328 exchCfg := exchInf[host] 329 if exchCfg == nil { 330 exchCfg, err = s.core.GetDEXConfig(host, cert) // cert is file contents, not name 331 if err != nil { 332 resErr := msgjson.NewError(msgjson.RPCGetDEXConfigError, "%v", err) 333 return createResponse(bondAssetsRoute, nil, resErr) 334 } 335 } 336 res := &getBondAssetsResponse{ 337 Expiry: exchCfg.BondExpiry, 338 Assets: exchCfg.BondAssets, 339 } 340 return createResponse(bondAssetsRoute, res, nil) 341 } 342 343 // handleGetDEXConfig handles requests for getdexconfig. 344 // *msgjson.ResponsePayload.Error is empty if successful. Requires the address 345 // of a dex and returns its config.. 346 func handleGetDEXConfig(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 347 host, cert, err := parseGetDEXConfigArgs(params) 348 if err != nil { 349 return usage(getDEXConfRoute, err) 350 } 351 exchange, err := s.core.GetDEXConfig(host, cert) // cert is file contents, not name 352 if err != nil { 353 resErr := msgjson.NewError(msgjson.RPCGetDEXConfigError, "%v", err) 354 return createResponse(getDEXConfRoute, nil, resErr) 355 } 356 return createResponse(getDEXConfRoute, exchange, nil) 357 } 358 359 // handleDiscoverAcct is the handler for discoveracct. *msgjson.ResponsePayload.Error 360 // is empty if successful. 361 func handleDiscoverAcct(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 362 form, err := parseDiscoverAcctArgs(params) 363 if err != nil { 364 return usage(discoverAcctRoute, err) 365 } 366 defer form.appPass.Clear() 367 _, paid, err := s.core.DiscoverAccount(form.addr, form.appPass, form.cert) 368 if err != nil { 369 resErr := &msgjson.Error{Code: msgjson.RPCDiscoverAcctError, Message: err.Error()} 370 return createResponse(discoverAcctRoute, nil, resErr) 371 } 372 return createResponse(discoverAcctRoute, &paid, nil) 373 } 374 375 func handleBondOptions(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 376 form, err := parseBondOptsArgs(params) 377 if err != nil { 378 return usage(bondOptionsRoute, err) 379 } 380 err = s.core.UpdateBondOptions(form) 381 if err != nil { 382 resErr := &msgjson.Error{Code: msgjson.RPCPostBondError, Message: err.Error()} 383 return createResponse(bondOptionsRoute, nil, resErr) 384 } 385 return createResponse(bondOptionsRoute, "ok", nil) 386 } 387 388 // handlePostBond handles requests for postbond. *msgjson.ResponsePayload.Error 389 // is empty if successful. 390 func handlePostBond(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 391 form, err := parsePostBondArgs(params) 392 if err != nil { 393 return usage(postBondRoute, err) 394 } 395 defer form.AppPass.Clear() 396 // Get the exchange config with Exchanges(), not GetDEXConfig, since we may 397 // already be connected and even with an existing account. 398 exchInf := s.core.Exchanges() 399 exchCfg := exchInf[form.Addr] 400 if exchCfg == nil { 401 // Not already registered. 402 exchCfg, err = s.core.GetDEXConfig(form.Addr, form.Cert) 403 if err != nil { 404 resErr := &msgjson.Error{Code: msgjson.RPCGetDEXConfigError, Message: err.Error()} 405 return createResponse(postBondRoute, nil, resErr) 406 } 407 } 408 // Registration with different assets will be supported in the future, but 409 // for now, this requires DCR. 410 assetID := uint32(42) 411 if form.Asset != nil { 412 assetID = *form.Asset 413 } 414 symb := dex.BipIDSymbol(assetID) 415 416 bondAsset, supported := exchCfg.BondAssets[symb] 417 if !supported { 418 resErr := msgjson.NewError(msgjson.RPCPostBondError, "DEX %s does not support registration with %s", form.Addr, symb) 419 return createResponse(postBondRoute, nil, resErr) 420 } 421 if bondAsset.Amt > form.Bond || form.Bond%bondAsset.Amt != 0 { 422 resErr := msgjson.NewError(msgjson.RPCPostBondError, "DEX at %s expects a bond amount in multiples of %d %s but %d was offered", 423 form.Addr, bondAsset.Amt, dex.BipIDSymbol(assetID), form.Bond) 424 return createResponse(postBondRoute, nil, resErr) 425 } 426 res, err := s.core.PostBond(form) 427 if err != nil { 428 resErr := &msgjson.Error{Code: msgjson.RPCPostBondError, Message: err.Error()} 429 return createResponse(postBondRoute, nil, resErr) 430 } 431 if res.BondID == "" { 432 return createResponse(postBondRoute, "existing account configured - no bond posted", nil) 433 } 434 return createResponse(postBondRoute, res, nil) 435 } 436 437 // handleExchanges handles requests for exchanges. It takes no arguments and 438 // returns a map of exchanges. 439 func handleExchanges(s *RPCServer, _ *RawParams) *msgjson.ResponsePayload { 440 // Convert something to a map[string]any. 441 convM := func(in any) map[string]any { 442 var m map[string]any 443 b, err := json.Marshal(in) 444 if err != nil { 445 panic(err) 446 } 447 if err = json.Unmarshal(b, &m); err != nil { 448 panic(err) 449 } 450 return m 451 } 452 res := s.core.Exchanges() 453 exchanges := convM(res) 454 // Iterate through exchanges converting structs into maps in order to 455 // remove some fields. Keys are DEX addresses. 456 for k, exchange := range exchanges { 457 exchangeDetails := convM(exchange) 458 // Remove a redundant address field. 459 delete(exchangeDetails, "host") 460 markets := convM(exchangeDetails["markets"]) 461 // Market keys are market name. 462 for k, market := range markets { 463 marketDetails := convM(market) 464 // Remove redundant name field. 465 delete(marketDetails, "name") 466 delete(marketDetails, "orders") 467 markets[k] = marketDetails 468 } 469 assets := convM(exchangeDetails["assets"]) 470 // Asset keys are assetIDs. 471 for k, asset := range assets { 472 assetDetails := convM(asset) 473 // Remove redundant id field. 474 delete(assetDetails, "id") 475 assets[k] = assetDetails 476 } 477 exchangeDetails["markets"] = markets 478 exchangeDetails["assets"] = assets 479 exchanges[k] = exchangeDetails 480 } 481 return createResponse(exchangesRoute, &exchanges, nil) 482 } 483 484 // handleLogin sets up the dex connections. 485 func handleLogin(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 486 appPass, err := parseLoginArgs(params) 487 if err != nil { 488 return usage(loginRoute, err) 489 } 490 defer appPass.Clear() 491 err = s.core.Login(appPass) 492 if err != nil { 493 resErr := msgjson.NewError(msgjson.RPCLoginError, "unable to login: %v", err) 494 return createResponse(loginRoute, nil, resErr) 495 } 496 res := "successfully logged in" 497 return createResponse(loginRoute, &res, nil) 498 } 499 500 // handleTrade handles requests for trade. *msgjson.ResponsePayload.Error is 501 // empty if successful. 502 func handleTrade(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 503 form, err := parseTradeArgs(params) 504 if err != nil { 505 return usage(tradeRoute, err) 506 } 507 defer form.appPass.Clear() 508 res, err := s.core.Trade(form.appPass, form.srvForm) 509 if err != nil { 510 resErr := msgjson.NewError(msgjson.RPCTradeError, "unable to trade: %v", err) 511 return createResponse(tradeRoute, nil, resErr) 512 } 513 tradeRes := &tradeResponse{ 514 OrderID: res.ID.String(), 515 Sig: res.Sig.String(), 516 Stamp: res.Stamp, 517 } 518 return createResponse(tradeRoute, &tradeRes, nil) 519 } 520 521 func handleMultiTrade(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 522 form, err := parseMultiTradeArgs(params) 523 if err != nil { 524 return usage(multiTradeRoute, err) 525 } 526 defer form.appPass.Clear() 527 results := s.core.MultiTrade(form.appPass, form.srvForm) 528 trades := make([]*tradeResponse, 0, len(results)) 529 for _, res := range results { 530 if res.Error != nil { 531 trades = append(trades, &tradeResponse{ 532 Error: res.Error, 533 }) 534 continue 535 } 536 trade := res.Order 537 trades = append(trades, &tradeResponse{ 538 OrderID: trade.ID.String(), 539 Sig: trade.Sig.String(), 540 Stamp: trade.Stamp, 541 }) 542 } 543 return createResponse(multiTradeRoute, &trades, nil) 544 } 545 546 // handleCancel handles requests for cancel. *msgjson.ResponsePayload.Error is 547 // empty if successful. 548 func handleCancel(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 549 form, err := parseCancelArgs(params) 550 if err != nil { 551 return usage(cancelRoute, err) 552 } 553 if err := s.core.Cancel(form.orderID); err != nil { 554 resErr := msgjson.NewError(msgjson.RPCCancelError, "unable to cancel order %q: %v", form.orderID, err) 555 return createResponse(cancelRoute, nil, resErr) 556 } 557 res := fmt.Sprintf(canceledOrderStr, form.orderID) 558 return createResponse(cancelRoute, &res, nil) 559 } 560 561 // handleWithdraw handles requests for withdraw. *msgjson.ResponsePayload.Error 562 // is empty if successful. 563 func handleWithdraw(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 564 return send(s, params, withdrawRoute) 565 } 566 567 // handleSend handles the request for send. *msgjson.ResponsePayload.Error 568 // is empty if successful. 569 func handleSend(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 570 return send(s, params, sendRoute) 571 } 572 573 func send(s *RPCServer, params *RawParams, route string) *msgjson.ResponsePayload { 574 form, err := parseSendOrWithdrawArgs(params) 575 if err != nil { 576 return usage(route, err) 577 } 578 defer form.appPass.Clear() 579 subtract := false 580 if route == withdrawRoute { 581 subtract = true 582 } 583 if len(form.appPass) == 0 { 584 resErr := msgjson.NewError(msgjson.RPCFundTransferError, "empty pass") 585 return createResponse(route, nil, resErr) 586 } 587 coin, err := s.core.Send(form.appPass, form.assetID, form.value, form.address, subtract) 588 if err != nil { 589 resErr := msgjson.NewError(msgjson.RPCFundTransferError, "unable to %s: %v", route, err) 590 return createResponse(route, nil, resErr) 591 } 592 res := coin.String() 593 return createResponse(route, &res, nil) 594 } 595 596 // handleRescanWallet handles requests to rescan a wallet. This may trigger an 597 // asynchronous resynchronization of wallet address activity, and the wallet 598 // state should be consulted for status. *msgjson.ResponsePayload.Error is empty 599 // if successful. 600 func handleRescanWallet(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 601 assetID, force, err := parseRescanWalletArgs(params) 602 if err != nil { 603 return usage(rescanWalletRoute, err) 604 } 605 err = s.core.RescanWallet(assetID, force) 606 if err != nil { 607 resErr := msgjson.NewError(msgjson.RPCWalletRescanError, "unable to rescan wallet: %v", err) 608 return createResponse(rescanWalletRoute, nil, resErr) 609 } 610 return createResponse(rescanWalletRoute, "started", nil) 611 } 612 613 // handleLogout logs out Bison Wallet. *msgjson.ResponsePayload.Error is empty 614 // if successful. 615 func handleLogout(s *RPCServer, _ *RawParams) *msgjson.ResponsePayload { 616 if err := s.core.Logout(); err != nil { 617 resErr := msgjson.NewError(msgjson.RPCLogoutError, "unable to logout: %v", err) 618 return createResponse(logoutRoute, nil, resErr) 619 } 620 res := logoutStr 621 return createResponse(logoutRoute, &res, nil) 622 } 623 624 // truncateOrderBook truncates book to the top nOrders of buys and sells. 625 func truncateOrderBook(book *core.OrderBook, nOrders uint64) { 626 truncFn := func(orders []*core.MiniOrder) []*core.MiniOrder { 627 if uint64(len(orders)) > nOrders { 628 // Nullify pointers stored in the unused part of the 629 // underlying array to allow for GC. 630 for i := nOrders; i < uint64(len(orders)); i++ { 631 orders[i] = nil 632 } 633 orders = orders[:nOrders] 634 635 } 636 return orders 637 } 638 book.Buys = truncFn(book.Buys) 639 book.Sells = truncFn(book.Sells) 640 } 641 642 // handleOrderBook handles requests for orderbook. 643 // *msgjson.ResponsePayload.Error is empty if successful. 644 func handleOrderBook(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 645 form, err := parseOrderBookArgs(params) 646 if err != nil { 647 return usage(orderBookRoute, err) 648 } 649 book, err := s.core.Book(form.host, form.base, form.quote) 650 if err != nil { 651 resErr := msgjson.NewError(msgjson.RPCOrderBookError, "unable to retrieve order book: %v", err) 652 return createResponse(orderBookRoute, nil, resErr) 653 } 654 if form.nOrders > 0 { 655 truncateOrderBook(book, form.nOrders) 656 } 657 return createResponse(orderBookRoute, book, nil) 658 } 659 660 // parseCoreOrder converts a *core.Order into a *myOrder. 661 func parseCoreOrder(co *core.Order, b, q uint32) *myOrder { 662 // matchesParser parses core.Match slice & calculates how much of the order 663 // has been settled/finalized. 664 parseMatches := func(matches []*core.Match) (ms []*match, settled uint64) { 665 ms = make([]*match, 0, len(matches)) 666 // coinSafeString gets the Coin's StringID safely. 667 coinSafeString := func(c *core.Coin) string { 668 if c == nil { 669 return "" 670 } 671 return c.StringID 672 } 673 for _, m := range matches { 674 // Sum up settled value. 675 if (m.Side == order.Maker && m.Status >= order.MakerRedeemed) || 676 (m.Side == order.Taker && m.Status >= order.MatchComplete) { 677 settled += m.Qty 678 } 679 match := &match{ 680 MatchID: m.MatchID.String(), 681 Status: m.Status.String(), 682 Revoked: m.Revoked, 683 Rate: m.Rate, 684 Qty: m.Qty, 685 Side: m.Side.String(), 686 FeeRate: m.FeeRate, 687 Stamp: m.Stamp, 688 IsCancel: m.IsCancel, 689 } 690 691 match.Swap = coinSafeString(m.Swap) 692 match.CounterSwap = coinSafeString(m.CounterSwap) 693 match.Redeem = coinSafeString(m.Redeem) 694 match.CounterRedeem = coinSafeString(m.CounterRedeem) 695 match.Refund = coinSafeString(m.Refund) 696 ms = append(ms, match) 697 } 698 return ms, settled 699 } 700 srvTime := time.UnixMilli(int64(co.Stamp)) 701 age := time.Since(srvTime).Round(time.Millisecond) 702 cancelling := co.Cancelling 703 // If the order is executed, canceled, or revoked, it is no longer cancelling. 704 if co.Status >= order.OrderStatusExecuted { 705 cancelling = false 706 } 707 o := &myOrder{ 708 Host: co.Host, 709 MarketName: co.MarketID, 710 BaseID: b, 711 QuoteID: q, 712 ID: co.ID.String(), 713 Type: co.Type.String(), 714 Sell: co.Sell, 715 Stamp: co.Stamp, 716 SubmitTime: co.SubmitTime, 717 Age: age.String(), 718 Rate: co.Rate, 719 Quantity: co.Qty, 720 Filled: co.Filled, 721 Status: co.Status.String(), 722 Cancelling: cancelling, 723 Canceled: co.Canceled, 724 TimeInForce: co.TimeInForce.String(), 725 } 726 727 // Parse matches & calculate settled value 728 o.Matches, o.Settled = parseMatches(co.Matches) 729 730 return o 731 } 732 733 // handleMyOrders handles requests for myorders. *msgjson.ResponsePayload.Error 734 // is empty if successful. 735 func handleMyOrders(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 736 form, err := parseMyOrdersArgs(params) 737 if err != nil { 738 return usage(myOrdersRoute, err) 739 } 740 var myOrders myOrdersResponse 741 filterMkts := form.base != nil && form.quote != nil 742 exchanges := s.core.Exchanges() 743 for host, exchange := range exchanges { 744 if form.host != "" && form.host != host { 745 continue 746 } 747 for _, market := range exchange.Markets { 748 if filterMkts && (market.BaseID != *form.base || market.QuoteID != *form.quote) { 749 continue 750 } 751 for _, order := range market.Orders { 752 myOrders = append(myOrders, parseCoreOrder(order, market.BaseID, market.QuoteID)) 753 } 754 for _, inFlight := range market.InFlightOrders { 755 myOrders = append(myOrders, parseCoreOrder(inFlight.Order, market.BaseID, market.QuoteID)) 756 } 757 } 758 } 759 return createResponse(myOrdersRoute, myOrders, nil) 760 } 761 762 // handleAppSeed handles requests for the app seed. *msgjson.ResponsePayload.Error 763 // is empty if successful. 764 func handleAppSeed(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 765 appPass, err := parseAppSeedArgs(params) 766 if err != nil { 767 return usage(appSeedRoute, err) 768 } 769 defer appPass.Clear() 770 seed, err := s.core.ExportSeed(appPass) 771 if err != nil { 772 resErr := msgjson.NewError(msgjson.RPCExportSeedError, "unable to retrieve app seed: %v", err) 773 return createResponse(appSeedRoute, nil, resErr) 774 } 775 776 return createResponse(appSeedRoute, seed, nil) 777 } 778 779 // handleDeleteArchivedRecords handles requests for deleting archived records. 780 // *msgjson.ResponsePayload.Error is empty if successful. 781 func handleDeleteArchivedRecords(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 782 form, err := parseDeleteArchivedRecordsArgs(params) 783 if err != nil { 784 return usage(deleteArchivedRecordsRoute, err) 785 } 786 nRecordsDeleted, err := s.core.DeleteArchivedRecords(form.olderThan, form.matchesFileStr, form.ordersFileStr) 787 if err != nil { 788 resErr := msgjson.NewError(msgjson.RPCDeleteArchivedRecordsError, "unable to delete records: %v", err) 789 return createResponse(deleteArchivedRecordsRoute, nil, resErr) 790 } 791 792 msg := fmt.Sprintf("%d archived records has been deleted successfully", nRecordsDeleted) 793 if nRecordsDeleted <= 0 { 794 msg = "No archived records found" 795 } 796 return createResponse(deleteArchivedRecordsRoute, msg, nil) 797 } 798 799 func handleWalletPeers(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 800 assetID, err := parseWalletPeersArgs(params) 801 if err != nil { 802 return usage(walletPeersRoute, err) 803 } 804 805 peers, err := s.core.WalletPeers(assetID) 806 if err != nil { 807 resErr := msgjson.NewError(msgjson.RPCWalletPeersError, "unable to get wallet peers: %v", err) 808 return createResponse(walletPeersRoute, nil, resErr) 809 } 810 return createResponse(walletPeersRoute, peers, nil) 811 } 812 813 func handleAddWalletPeer(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 814 form, err := parseAddRemoveWalletPeerArgs(params) 815 if err != nil { 816 return usage(addWalletPeerRoute, err) 817 } 818 819 err = s.core.AddWalletPeer(form.assetID, form.address) 820 if err != nil { 821 resErr := msgjson.NewError(msgjson.RPCWalletPeersError, "unable to add wallet peer: %v", err) 822 return createResponse(addWalletPeerRoute, nil, resErr) 823 } 824 825 return createResponse(addWalletPeerRoute, "successfully added peer", nil) 826 } 827 828 func handleRemoveWalletPeer(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 829 form, err := parseAddRemoveWalletPeerArgs(params) 830 if err != nil { 831 return usage(removeWalletPeerRoute, err) 832 } 833 834 err = s.core.RemoveWalletPeer(form.assetID, form.address) 835 if err != nil { 836 resErr := msgjson.NewError(msgjson.RPCWalletPeersError, "unable to remove wallet peer: %v", err) 837 return createResponse(removeWalletPeerRoute, nil, resErr) 838 } 839 840 return createResponse(removeWalletPeerRoute, "successfully removed peer", nil) 841 } 842 843 func handleNotifications(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 844 numNotes, err := parseNotificationsArgs(params) 845 if err != nil { 846 return usage(notificationsRoute, err) 847 } 848 849 notes, _, err := s.core.Notifications(numNotes) 850 if err != nil { 851 resErr := msgjson.NewError(msgjson.RPCNotificationsError, "unable to handle notification: %v", err) 852 return createResponse(notificationsRoute, nil, resErr) 853 } 854 855 return createResponse(notificationsRoute, notes, nil) 856 } 857 858 func handleMMAvailableBalances(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 859 form, err := parseMMAvailableBalancesArgs(params) 860 if err != nil { 861 return usage(mmAvailableBalancesRoute, err) 862 } 863 864 dexBalances, cexBalances, err := s.mm.AvailableBalances(form.mkt, &form.cfgFilePath) 865 if err != nil { 866 resErr := msgjson.NewError(msgjson.RPCMMAvailableBalancesError, "unable to get available balances: %v", err) 867 return createResponse(mmAvailableBalancesRoute, nil, resErr) 868 } 869 870 res := struct { 871 DEXBalances map[uint32]uint64 `json:"dexBalances"` 872 CEXBalances map[uint32]uint64 `json:"cexBalances"` 873 }{ 874 DEXBalances: dexBalances, 875 CEXBalances: cexBalances, 876 } 877 878 return createResponse(mmAvailableBalancesRoute, res, nil) 879 } 880 881 func handleStartBot(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 882 form, err := parseStartBotArgs(params) 883 if err != nil { 884 return usage(startBotRoute, err) 885 } 886 887 err = s.mm.StartBot(&mm.StartConfig{MarketWithHost: *form.mkt}, &form.cfgFilePath, form.appPass, true) 888 if err != nil { 889 resErr := msgjson.NewError(msgjson.RPCStartMarketMakingError, "unable to start market making: %v", err) 890 return createResponse(startBotRoute, nil, resErr) 891 } 892 893 return createResponse(startBotRoute, "started bot", nil) 894 } 895 896 func handleStopBot(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 897 mkt, err := parseStopBotArgs(params) 898 if err != nil { 899 return usage(startBotRoute, err) 900 } 901 902 err = s.mm.StopBot(mkt) 903 if err != nil { 904 resErr := msgjson.NewError(msgjson.RPCStopMarketMakingError, "unable to stop market making: %v", err) 905 return createResponse(stopBotRoute, nil, resErr) 906 } 907 908 return createResponse(stopBotRoute, "stopped bot", nil) 909 } 910 911 func handleUpdateRunningBotCfg(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 912 form, err := parseUpdateRunningBotArgs(params) 913 if err != nil { 914 return usage(updateRunningBotCfgRoute, err) 915 } 916 917 data, err := os.ReadFile(form.cfgFilePath) 918 if err != nil { 919 resErr := msgjson.NewError(msgjson.RPCUpdateRunningBotCfgError, "unable to read config file: %v", err) 920 return createResponse(updateRunningBotCfgRoute, nil, resErr) 921 } 922 923 cfg := &mm.MarketMakingConfig{} 924 err = json.Unmarshal(data, cfg) 925 if err != nil { 926 resErr := msgjson.NewError(msgjson.RPCUpdateRunningBotCfgError, "unable to unmarshal config: %v", err) 927 return createResponse(updateRunningBotCfgRoute, nil, resErr) 928 } 929 930 var botCfg *mm.BotConfig 931 for _, bot := range cfg.BotConfigs { 932 if bot.Host == form.mkt.Host && bot.BaseID == form.mkt.BaseID && bot.QuoteID == form.mkt.QuoteID { 933 botCfg = bot 934 break 935 } 936 } 937 938 if botCfg == nil { 939 resErr := msgjson.NewError(msgjson.RPCUpdateRunningBotCfgError, "bot config not found for market %s", form.mkt.String()) 940 return createResponse(updateRunningBotCfgRoute, nil, resErr) 941 } 942 943 err = s.mm.UpdateRunningBotCfg(botCfg, form.balances, false) 944 if err != nil { 945 resErr := msgjson.NewError(msgjson.RPCUpdateRunningBotCfgError, "unable to update running bot: %v", err) 946 return createResponse(updateRunningBotCfgRoute, nil, resErr) 947 } 948 949 return createResponse(updateRunningBotCfgRoute, "updated running bot", nil) 950 } 951 952 func handleUpdateRunningBotInventory(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 953 form, err := parseUpdateRunningBotInventoryArgs(params) 954 if err != nil { 955 return usage(updateRunningBotCfgRoute, err) 956 } 957 958 err = s.mm.UpdateRunningBotInventory(form.mkt, form.balances) 959 if err != nil { 960 resErr := msgjson.NewError(msgjson.RPCUpdateRunningBotInvError, "unable to update running bot: %v", err) 961 return createResponse(updateRunningBotCfgRoute, nil, resErr) 962 } 963 964 return createResponse(updateRunningBotCfgRoute, "updated running bot", nil) 965 } 966 967 func handleMMStatus(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 968 status := s.mm.RunningBotsStatus() 969 return createResponse(mmStatusRoute, status, nil) 970 } 971 972 func handleSetVSP(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 973 form, err := parseSetVSPArgs(params) 974 if err != nil { 975 return usage(setVSPRoute, err) 976 } 977 978 err = s.core.SetVSP(form.assetID, form.addr) 979 if err != nil { 980 resErr := msgjson.NewError(msgjson.RPCSetVSPError, "unable to set vsp: %v", err) 981 return createResponse(setVSPRoute, nil, resErr) 982 } 983 984 return createResponse(setVSPRoute, fmt.Sprintf(setVSPStr, form.addr), nil) 985 } 986 987 func handlePurchaseTickets(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 988 form, err := parsePurchaseTicketsArgs(params) 989 if err != nil { 990 return usage(purchaseTicketsRoute, err) 991 } 992 defer form.appPass.Clear() 993 994 if err = s.core.PurchaseTickets(form.assetID, form.appPass, form.num); err != nil { 995 resErr := msgjson.NewError(msgjson.RPCPurchaseTicketsError, "unable to purchase tickets: %v", err) 996 return createResponse(purchaseTicketsRoute, nil, resErr) 997 } 998 999 return createResponse(purchaseTicketsRoute, true, nil) 1000 } 1001 1002 func handleStakeStatus(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 1003 assetID, err := parseStakeStatusArgs(params) 1004 if err != nil { 1005 return usage(stakeStatusRoute, err) 1006 } 1007 stakeStatus, err := s.core.StakeStatus(assetID) 1008 if err != nil { 1009 resErr := msgjson.NewError(msgjson.RPCStakeStatusError, "unable to get staking status: %v", err) 1010 return createResponse(stakeStatusRoute, nil, resErr) 1011 } 1012 1013 return createResponse(stakeStatusRoute, &stakeStatus, nil) 1014 } 1015 1016 func handleSetVotingPreferences(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 1017 form, err := parseSetVotingPreferencesArgs(params) 1018 if err != nil { 1019 return usage(setVotingPreferencesRoute, err) 1020 } 1021 1022 err = s.core.SetVotingPreferences(form.assetID, form.voteChoices, form.tSpendPolicy, form.treasuryPolicy) 1023 if err != nil { 1024 resErr := msgjson.NewError(msgjson.RPCSetVotingPreferencesError, "unable to set voting preferences: %v", err) 1025 return createResponse(setVotingPreferencesRoute, nil, resErr) 1026 } 1027 1028 return createResponse(setVotingPreferencesRoute, "vote preferences set", nil) 1029 } 1030 1031 func handleTxHistory(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 1032 form, err := parseTxHistoryArgs(params) 1033 if err != nil { 1034 return usage(txHistoryRoute, err) 1035 } 1036 1037 txs, err := s.core.TxHistory(form.assetID, form.num, form.refID, form.past) 1038 if err != nil { 1039 resErr := msgjson.NewError(msgjson.RPCTxHistoryError, "unable to get tx history: %v", err) 1040 return createResponse(txHistoryRoute, nil, resErr) 1041 } 1042 1043 return createResponse(txHistoryRoute, txs, nil) 1044 } 1045 1046 func handleWalletTx(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 1047 form, err := parseWalletTxArgs(params) 1048 if err != nil { 1049 return usage(walletTxRoute, err) 1050 } 1051 1052 tx, err := s.core.WalletTransaction(form.assetID, form.txID) 1053 if err != nil { 1054 resErr := msgjson.NewError(msgjson.RPCTxHistoryError, "unable to get wallet tx: %v", err) 1055 return createResponse(walletTxRoute, nil, resErr) 1056 } 1057 1058 return createResponse(walletTxRoute, tx, nil) 1059 } 1060 1061 func handleWithdrawBchSpv(s *RPCServer, params *RawParams) *msgjson.ResponsePayload { 1062 appPW, recipient, err := parseBchWithdrawArgs(params) 1063 if err != nil { 1064 return usage(withdrawBchSpvRoute, err) 1065 } 1066 defer appPW.Clear() 1067 1068 txB, err := s.core.GenerateBCHRecoveryTransaction(appPW, recipient) 1069 if err != nil { 1070 resErr := msgjson.NewError(msgjson.RPCCreateWalletError, "error generating tx: %v", err) 1071 return createResponse(withdrawBchSpvRoute, nil, resErr) 1072 } 1073 1074 return createResponse(withdrawBchSpvRoute, dex.Bytes(txB).String(), nil) 1075 } 1076 1077 // format concatenates thing and tail. If thing is empty, returns an empty 1078 // string. 1079 func format(thing, tail string) string { 1080 if thing == "" { 1081 return "" 1082 } 1083 return fmt.Sprintf("%s%s", thing, tail) 1084 } 1085 1086 // ListCommands prints a short usage string for every route available to the 1087 // rpcserver. 1088 func ListCommands(includePasswords bool) string { 1089 var sb strings.Builder 1090 var err error 1091 for _, r := range sortHelpKeys() { 1092 msg := helpMsgs[r] 1093 // If help should include password arguments and this command 1094 // has password arguments, add them to the help message. 1095 if includePasswords && msg.pwArgsShort != "" { 1096 _, err = sb.WriteString(fmt.Sprintf("%s %s%s\n", r, 1097 format(msg.pwArgsShort, " "), msg.argsShort)) 1098 } else { 1099 _, err = sb.WriteString(fmt.Sprintf("%s %s\n", r, msg.argsShort)) 1100 } 1101 if err != nil { 1102 log.Errorf("unable to parse help message for %s", r) 1103 return "" 1104 } 1105 } 1106 s := sb.String() 1107 // Remove trailing newline. 1108 return s[:len(s)-1] 1109 } 1110 1111 // commandUsage returns a help message for cmd or an error if cmd is unknown. 1112 func commandUsage(cmd string, includePasswords bool) (string, error) { 1113 msg, exists := helpMsgs[cmd] 1114 if !exists { 1115 return "", fmt.Errorf("%w: %s", errUnknownCmd, cmd) 1116 } 1117 // If help should include password arguments and this command has 1118 // password arguments, return them as part of the help message. 1119 if includePasswords && msg.pwArgsShort != "" { 1120 return fmt.Sprintf("%s %s%s\n\n%s\n\n%s%s%s", 1121 cmd, format(msg.pwArgsShort, " "), msg.argsShort, 1122 msg.cmdSummary, format(msg.pwArgsLong, "\n\n"), format(msg.argsLong, "\n\n"), 1123 msg.returns), nil 1124 } 1125 return fmt.Sprintf("%s %s\n\n%s\n\n%s%s", cmd, msg.argsShort, 1126 msg.cmdSummary, format(msg.argsLong, "\n\n"), msg.returns), nil 1127 } 1128 1129 // sortHelpKeys returns a sorted list of helpMsgs keys. 1130 func sortHelpKeys() []string { 1131 keys := make([]string, 0, len(helpMsgs)) 1132 for k := range helpMsgs { 1133 keys = append(keys, k) 1134 } 1135 sort.Slice(keys, func(i, j int) bool { 1136 return keys[i] < keys[j] 1137 }) 1138 return keys 1139 } 1140 1141 type helpMsg struct { 1142 pwArgsShort, argsShort, cmdSummary, pwArgsLong, argsLong, returns string 1143 } 1144 1145 // helpMsgs are a map of routes to help messages. They are broken down into six 1146 // sections. 1147 // In descending order: 1148 // 1. Password argument example inputs. These are arguments the caller may not 1149 // want to echo listed in order of input. 1150 // 2. Argument example inputs. These are non-sensitive arguments listed in 1151 // order of input. 1152 // 3. A description of the command. 1153 // 4. An extensive breakdown of the password arguments. 1154 // 5. An extensive breakdown of the arguments. 1155 // 6. An extensive breakdown of the returned values. 1156 var helpMsgs = map[string]helpMsg{ 1157 helpRoute: { 1158 pwArgsShort: ``, // password args example input 1159 argsShort: `("cmd") (includePasswords)`, // args example input 1160 cmdSummary: `Print a help message.`, // command explanation 1161 pwArgsLong: ``, // password args breakdown 1162 argsLong: `Args: 1163 cmd (string): Optional. The command to print help for. 1164 includePasswords (bool): Optional. Default is false. Whether to include 1165 password arguments in the returned help.`, // args breakdown 1166 returns: `Returns: 1167 string: The help message for command.`, // returns breakdown 1168 }, 1169 versionRoute: { 1170 cmdSummary: `Print the Bison Wallet rpcserver version.`, 1171 returns: `Returns: 1172 string: The Bison Wallet rpcserver version.`, 1173 }, 1174 discoverAcctRoute: { 1175 pwArgsShort: `"appPass"`, 1176 argsShort: `"addr" ("cert")`, 1177 cmdSummary: `Discover an account that is used for a dex. Useful when restoring 1178 an account and can be used in place of register. Will error if 1179 the account has already been discovered/restored.`, 1180 pwArgsLong: `Password Args: 1181 appPass (string): The Bison Wallet password.`, 1182 argsLong: `Args: 1183 addr (string): The DEX address to discover an account for. 1184 cert (string): Optional. The TLS certificate path.`, 1185 returns: `Returns: 1186 bool: True if the account has has been registered and paid for.`, 1187 }, 1188 initRoute: { 1189 pwArgsShort: `"appPass"`, 1190 argsShort: `("seed")`, 1191 cmdSummary: `Initialize the client.`, 1192 pwArgsLong: `Password Args: 1193 appPass (string): The Bison Wallet password.`, 1194 argsLong: `Args: 1195 seed (string): Optional. hex-encoded 512-bit restoration seed.`, 1196 returns: `Returns: 1197 string: The message "` + initializedStr + `"`, 1198 }, 1199 deleteArchivedRecordsRoute: { 1200 argsShort: `("unix time milli") ("matches csv path") ("orders csv path")`, 1201 cmdSummary: `Delete archived records from the database and returns total deleted. Optionally 1202 set a time to delete records before and file paths to save deleted records as comma separated 1203 values. Note that file locations are from the perspective of dexc and not the caller.`, 1204 argsLong: `Args: 1205 unix time milli (int): Optional. If set deletes records before the date in unix time 1206 in milliseconds (not seconds). Unset or 0 will default to the current time. 1207 matches csv path (string): Optional. A path to save a csv with deleted matches. 1208 Will not save by default. 1209 orders csv path (string): Optional. A path to save a csv with deleted orders. 1210 Will not save by default.`, 1211 returns: `Returns: 1212 Nothing.`, 1213 }, 1214 bondAssetsRoute: { 1215 argsShort: `"dex" ("cert")`, 1216 cmdSummary: `Get dex bond asset config.`, 1217 argsLong: `Args: 1218 dex (string): The dex address to get bond info for. 1219 cert (string): Optional. The TLS certificate path.`, 1220 returns: `Returns: 1221 obj: The getBondAssets result. 1222 { 1223 "expiry" (int): Bond expiry in seconds remaining until locktime. 1224 "assets" (object): { 1225 "id" (int): The BIP-44 coin type for the asset. 1226 "confs" (int): The required confirmations for the bond transaction. 1227 "amount" (int): The minimum bond amount. 1228 } 1229 }`, 1230 }, 1231 getDEXConfRoute: { 1232 argsShort: `"dex" ("cert")`, 1233 cmdSummary: `Get a DEX configuration.`, 1234 argsLong: `Args: 1235 dex (string): The dex address to get config for. 1236 cert (string): Optional. The TLS certificate path.`, 1237 returns: `Returns: 1238 obj: The getdexconfig result. See the 'exchanges' result.`, 1239 }, 1240 newWalletRoute: { 1241 pwArgsShort: `"appPass" "walletPass"`, 1242 argsShort: `assetID walletType ("path" "settings")`, 1243 cmdSummary: `Connect to a new wallet.`, 1244 pwArgsLong: `Password Args: 1245 appPass (string): The Bison Wallet password. 1246 walletPass (string): The wallet's password. Leave the password empty for wallets without a password set.`, 1247 argsLong: `Args: 1248 assetID (int): The asset's BIP-44 registered coin index. e.g. 42 for DCR. 1249 See https://github.com/satoshilabs/slips/blob/master/slip-0044.md 1250 walletType (string): The wallet type. 1251 path (string): Optional. The path to a configuration file. 1252 settings (string): A JSON-encoded string->string mapping of additional 1253 configuration settings. These settings take precedence over any settings 1254 parsed from file. e.g. '{"account":"default"}' for Decred accounts, and 1255 '{"walletname":""}' for the default Bitcoin wallet where bitcoind's listwallets RPC gives possible walletnames.`, 1256 returns: `Returns: 1257 string: The message "` + fmt.Sprintf(walletCreatedStr, "[coin symbol]") + `"`, 1258 }, 1259 openWalletRoute: { 1260 pwArgsShort: `"appPass"`, 1261 argsShort: `assetID`, 1262 cmdSummary: `Open an existing wallet.`, 1263 pwArgsLong: `Password Args: 1264 appPass (string): The Bison Wallet password.`, 1265 argsLong: `Args: 1266 assetID (int): The asset's BIP-44 registered coin index. e.g. 42 for DCR. 1267 See https://github.com/satoshilabs/slips/blob/master/slip-0044.md`, 1268 returns: `Returns: 1269 string: The message "` + fmt.Sprintf(walletUnlockedStr, "[coin symbol]") + `"`, 1270 }, 1271 closeWalletRoute: { 1272 argsShort: `assetID`, 1273 cmdSummary: `Close an open wallet.`, 1274 argsLong: `Args: 1275 assetID (int): The asset's BIP-44 registered coin index. e.g. 42 for DCR. 1276 See https://github.com/satoshilabs/slips/blob/master/slip-0044.md`, 1277 returns: `Returns: 1278 string: The message "` + fmt.Sprintf(walletLockedStr, "[coin symbol]") + `"`, 1279 }, 1280 toggleWalletStatusRoute: { 1281 pwArgsShort: "appPass", 1282 argsShort: `assetID disable`, 1283 cmdSummary: `Disable or enable an existing wallet. When disabling a chain's primary asset wallet, 1284 all token wallets for that chain will be disabled too.`, 1285 pwArgsLong: `Password Args: 1286 appPass (string): The Bison Wallet password.`, 1287 argsLong: `Args: 1288 assetID (int): The asset's BIP-44 registered coin index. e.g. 42 for DCR. 1289 See https://github.com/satoshilabs/slips/blob/master/slip-0044.md 1290 disable (bool): The wallet's status. e.g To disable a wallet set to "true", to enable set to "false".`, 1291 returns: `Returns: 1292 string: The message "` + fmt.Sprintf(walletStatusStr, "[coin symbol]", "[wallet status]") + `".`, 1293 }, 1294 walletsRoute: { 1295 cmdSummary: `List all wallets.`, 1296 returns: `Returns: 1297 array: An array of wallet results. 1298 [ 1299 { 1300 "symbol" (string): The coin symbol. 1301 "assetID" (int): The asset's BIP-44 registered coin index. e.g. 42 for DCR. 1302 See https://github.com/satoshilabs/slips/blob/master/slip-0044.md 1303 "open" (bool): Whether the wallet is unlocked. 1304 "running" (bool): Whether the wallet is running. 1305 "disabled" (bool): Whether the wallet is disabled. 1306 "updated" (int): Unix time of last balance update. Seconds since 00:00:00 Jan 1 1970. 1307 "balance" (obj): { 1308 "available" (int): The balance available for funding orders case. 1309 "immature" (int): Balance that requires confirmations before use. 1310 "locked" (int): The total locked balance. 1311 "stamp" (string): Time stamp. 1312 } 1313 "address" (string): A wallet address. 1314 "feerate" (int): The fee rate. 1315 "units" (string): Unit of measure for amounts. 1316 },... 1317 ]`, 1318 }, 1319 postBondRoute: { 1320 pwArgsShort: `"appPass"`, 1321 argsShort: `"addr" bond assetID (maintain "cert")`, 1322 cmdSummary: `Post new bond for DEX. An ok response does not mean that the bond is active. 1323 Bond is active after the bond transaction has been confirmed and the server notified.`, 1324 pwArgsLong: `Password Args: 1325 appPass (string): The Bison Wallet password.`, 1326 argsLong: `Args: 1327 addr (string): The DEX address to post bond for for. 1328 bond (int): The bond amount (in DCR presently). 1329 assetID (int): The asset ID with which to pay the fee. 1330 maintain (bool): Optional. Whether to maintain the trading tier established by this bond. Only applicable when registering. (default is true) 1331 cert (string): Optional. The TLS certificate path. Only applicable when registering.`, 1332 returns: `Returns: 1333 { 1334 "bondID" (string): The bond transactions's txid and output index. 1335 "reqConfirms" (int): The number of confirmations required to start trading. 1336 }`, 1337 }, 1338 bondOptionsRoute: { 1339 argsShort: `"addr" targetTier (maxBondedAmt bondAssetID penaltyComps)`, 1340 cmdSummary: `Change bond options for a DEX.`, 1341 argsLong: `Args: 1342 addr (string): The DEX address to post bond for for. 1343 targetTier (int): The target trading tier. 1344 maxBondedAmt (int): The maximum amount that may be locked in bonds. 1345 bondAssetID (int): The asset ID with which to auto-post bonds. 1346 penaltyComp (int): The maximum number of penalties to compensate`, 1347 returns: `Returns: "ok"`, 1348 }, 1349 exchangesRoute: { 1350 cmdSummary: `Detailed information about known exchanges and markets.`, 1351 returns: `Returns: 1352 obj: The exchanges result. 1353 { 1354 "[DEX host]": { 1355 "acctID" (string): The client's account ID associated with this DEX., 1356 "markets": { 1357 "[assetID-assetID]": { 1358 "baseid" (int): The base asset ID 1359 "basesymbol" (string): The base ticker symbol. 1360 "quoteid" (int): The quote asset ID. 1361 "quotesymbol" (string): The quote asset ID symbol, 1362 "epochlen" (int): Duration of a epoch in milliseconds. 1363 "startepoch" (int): Time of start of the last epoch in milliseconds 1364 since 00:00:00 Jan 1 1970. 1365 "buybuffer" (float): The minimum order size for a market buy order. 1366 },... 1367 }, 1368 "assets": { 1369 "[assetID]": { 1370 "symbol" (string): The asset's coin symbol. 1371 "lotSize" (int): The amount of units of a coin in one lot. 1372 "rateStep" (int): the price rate increment in atoms. 1373 "feeRate" (int): The transaction fee in atoms per byte. 1374 "swapSize" (int): The size of a swap transaction in bytes. 1375 "swapSizeBase" (int): The size of a swap transaction minus inputs in bytes. 1376 "swapConf" (int): The number of confirmations needed to confirm 1377 trade transactions. 1378 },... 1379 }, 1380 "regFees": { 1381 "[assetSymbol]": { 1382 "id" (int): The asset's BIP-44 coin ID. 1383 "confs" (int): The number of confirmations required. 1384 "amt" (int): The fee amount. 1385 },... 1386 } 1387 },... 1388 }`, 1389 }, 1390 loginRoute: { 1391 pwArgsShort: `"appPass"`, 1392 cmdSummary: `Attempt to login to all registered DEX servers.`, 1393 pwArgsLong: `Password Args: 1394 appPass (string): The Bison Wallet password.`, 1395 returns: `Returns: 1396 obj: A map of notifications and dexes. 1397 { 1398 "notifications" (array): An array of most recent notifications. 1399 [ 1400 { 1401 "type" (string): The notification type. 1402 "subject" (string): A clarification of type. 1403 "details"(string): The notification details. 1404 "severity" (int): The importance of the notification on a scale of 0 1405 through 5. 1406 "stamp" (int): Unix time of the notification. Seconds since 00:00:00 Jan 1 1970. 1407 "acked" (bool): Whether the notification was acknowledged. 1408 "id" (string): A unique hex ID. 1409 },... 1410 ], 1411 "dexes" (array): An array of login attempted dexes. 1412 [ 1413 { 1414 "host" (string): The DEX address. 1415 "acctID" (string): A unique hex ID. 1416 "authed" (bool): Whether the dex has been successfully authed. 1417 "autherr" (string): Omitted if authed. If not authed, the reason. 1418 "tradeIDs" (array): An array of active trade IDs. 1419 },... 1420 ] 1421 }`, 1422 }, 1423 tradeRoute: { 1424 pwArgsShort: `"appPass"`, 1425 argsShort: `"host" isLimit sell base quote qty rate immediate`, 1426 cmdSummary: `Make an order to buy or sell an asset.`, 1427 pwArgsLong: `Password Args: 1428 appPass (string): The Bison Wallet password.`, 1429 argsLong: `Args: 1430 host (string): The DEX to trade on. 1431 isLimit (bool): Whether the order is a limit order. 1432 sell (bool): Whether the order is selling. 1433 base (int): The BIP-44 coin index for the market's base asset. 1434 quote (int): The BIP-44 coin index for the market's quote asset. 1435 qty (int): The number of units to buy/sell. Must be a multiple of the lot size. 1436 rate (int): The atoms quote asset to pay/accept per unit base asset. e.g. 1437 156000 satoshi/DCR for the DCR(base)_BTC(quote). 1438 immediate (bool): Require immediate match. Do not book the order. 1439 options (string): A JSON-encoded string->string mapping of additional 1440 trade options.`, 1441 returns: `Returns: 1442 obj: The order details. 1443 { 1444 "orderid" (string): The order's unique hex identifier. 1445 "sig" (string): The DEX's signature of the order information. 1446 "stamp" (int): The time the order was signed in milliseconds since 00:00:00 1447 Jan 1 1970. 1448 }`, 1449 }, 1450 multiTradeRoute: { 1451 pwArgsShort: `"appPass"`, 1452 argsShort: `"host" sell base quote maxLock [[qty,rate]] options`, 1453 cmdSummary: `Place multiple orders in one go.`, 1454 pwArgsLong: `Password Args: 1455 appPass (string): The Bison Wallet password.`, 1456 argsLong: `Args: 1457 host (string): The DEX to trade on. 1458 sell (bool): Whether the order is selling. 1459 base (int): The BIP-44 coin index for the market's base asset. 1460 quote (int): The BIP-44 coin index for the market's quote asset. 1461 maxLock (int): The maximum amount the wallet can lock for this order. 0 means no limit. 1462 placements ([[int,int]]): An array of [qty,rate] placements. Quantity must be 1463 a multiple of the lot size. Rate must be in atomic units of the quote asset. 1464 options (string): A JSON-encoded string->string mapping of additional 1465 trade options.`, 1466 returns: `Returns: 1467 obj: The details of each order. 1468 [{ 1469 "orderid" (string): The order's unique hex identifier. 1470 "sig" (string): The DEX's signature of the order information. 1471 "stamp" (int): The time the order was signed in milliseconds since 00:00:00 1472 Jan 1 1970. 1473 }]`, 1474 }, 1475 cancelRoute: { 1476 pwArgsShort: `"appPass"`, 1477 argsShort: `"orderID"`, 1478 cmdSummary: `Cancel an order.`, 1479 pwArgsLong: `Password Args: 1480 appPass (string): The Bison Wallet password.`, 1481 argsLong: `Args: 1482 orderID (string): The hex ID of the order to cancel`, 1483 returns: `Returns: 1484 string: The message "` + fmt.Sprintf(canceledOrderStr, "[order ID]") + `"`, 1485 }, 1486 rescanWalletRoute: { 1487 argsShort: `assetID (force)`, 1488 cmdSummary: `Initiate a rescan of an asset's wallet. This is only supported for certain 1489 wallet types. Wallet resynchronization may be asynchronous, and the wallet 1490 state should be consulted for progress. 1491 1492 WARNING: It is ill-advised to initiate a wallet rescan with active orders 1493 unless as a last ditch effort to get the wallet to recognize a transaction 1494 needed to complete a swap.`, 1495 returns: `Returns: 1496 string: "started"`, 1497 argsLong: `Args: 1498 assetID (int): The asset's BIP-44 registered coin index. Used to identify 1499 which wallet to withdraw from. e.g. 42 for DCR. See 1500 https://github.com/satoshilabs/slips/blob/master/slip-0044.md 1501 force (bool): Force a wallet rescan even if their are active orders. The 1502 default is false.`, 1503 }, 1504 withdrawRoute: { 1505 pwArgsShort: `"appPass"`, 1506 argsShort: `assetID value "address"`, 1507 cmdSummary: `Withdraw value from an exchange wallet to address. Fees are subtracted from the value.`, 1508 pwArgsLong: `Password Args: 1509 appPass (string): The Bison Wallet password.`, 1510 argsLong: `Args: 1511 assetID (int): The asset's BIP-44 registered coin index. Used to identify 1512 which wallet to withdraw from. e.g. 42 for DCR. See 1513 https://github.com/satoshilabs/slips/blob/master/slip-0044.md 1514 value (int): The amount to withdraw in units of the asset's smallest 1515 denomination (e.g. satoshis, atoms, etc.)" 1516 address (string): The address to which withdrawn funds are sent.`, 1517 returns: `Returns: 1518 string: "[coin ID]"`, 1519 }, 1520 sendRoute: { 1521 pwArgsShort: `"appPass"`, 1522 argsShort: `assetID value "address"`, 1523 cmdSummary: `Sends exact value from an exchange wallet to address.`, 1524 pwArgsLong: `Password Args: 1525 appPass (string): The Bison Wallet password.`, 1526 argsLong: `Args: 1527 assetID (int): The asset's BIP-44 registered coin index. Used to identify 1528 which wallet to withdraw from. e.g. 42 for DCR. See 1529 https://github.com/satoshilabs/slips/blob/master/slip-0044.md 1530 value (int): The amount to send in units of the asset's smallest 1531 denomination (e.g. satoshis, atoms, etc.)" 1532 address (string): The address to which funds are sent.`, 1533 returns: `Returns: 1534 string: "[coin ID]"`, 1535 }, 1536 logoutRoute: { 1537 cmdSummary: `Logout of Bison Wallet.`, 1538 returns: `Returns: 1539 string: The message "` + logoutStr + `"`, 1540 }, 1541 orderBookRoute: { 1542 argsShort: `"host" base quote (nOrders)`, 1543 cmdSummary: `Retrieve all orders for a market.`, 1544 argsLong: `Args: 1545 host (string): The DEX to retrieve the order book from. 1546 base (int): The BIP-44 coin index for the market's base asset. 1547 quote (int): The BIP-44 coin index for the market's quote asset. 1548 nOrders (int): Optional. Default is 0, which returns all orders. The number 1549 of orders from the top of buys and sells to return. Epoch orders are not 1550 truncated.`, 1551 returns: `Returns: 1552 obj: A map of orders. 1553 { 1554 "sells" (array): An array of booked sell orders. 1555 [ 1556 { 1557 "qty" (float): The number of coins base asset being sold. 1558 "rate" (float): The coins quote asset to pay per coin base asset. 1559 "sell" (bool): Always true because this is a sell order. 1560 "token" (string): The first 8 bytes of the order id, coded in hex. 1561 },... 1562 ], 1563 "buys" (array): An array of booked buy orders. 1564 [ 1565 { 1566 "qty" (float): The number of coins base asset being bought. 1567 "rate" (float): The coins quote asset to accept per coin base asset. 1568 "sell" (bool): Always false because this is a buy order. 1569 "token" (string): The first 8 bytes of the order id, coded in hex. 1570 },... 1571 ], 1572 "epoch" (array): An array of epoch orders. Epoch orders include all kinds 1573 of orders, even those that cannot or may not be booked. They are not 1574 truncated. 1575 [ 1576 { 1577 "qty" (float): The number of coins base asset being bought or sold. 1578 "rate" (float): The coins quote asset to accept per coin base asset. 1579 "sell" (bool): Whether this order is a sell order. 1580 "token" (string): The first 8 bytes of the order id, coded in hex. 1581 "epoch" (int): The order's epoch. 1582 },... 1583 ], 1584 }`, 1585 }, 1586 myOrdersRoute: { 1587 argsShort: `("host") (base) (quote)`, 1588 cmdSummary: `Fetch all active and recently executed orders 1589 belonging to the user.`, 1590 argsLong: `Args: 1591 host (string): Optional. The DEX to show orders from. 1592 base (int): Optional. The BIP-44 coin index for the market's base asset. 1593 quote (int): Optional. The BIP-44 coin index for the market's quote asset.`, 1594 returns: `Returns: 1595 array: An array of orders. 1596 [ 1597 { 1598 "host" (string): The DEX address. 1599 "marketName" (string): The market's name. e.g. "DCR_BTC". 1600 "baseID" (int): The market's base asset BIP-44 coin index. e.g. 42 for DCR. 1601 "quoteID" (int): The market's quote asset BIP-44 coin index. e.g. 0 for BTC. 1602 "id" (string): The order's unique hex ID. 1603 "type" (string): The type of order. "limit", "market", or "cancel". 1604 "sell" (string): Whether this order is selling. 1605 "stamp" (int): Server's time stamp of the order in milliseconds since 00:00:00 Jan 1 1970. 1606 "submitTime" (int): Time of order submission, also in milliseconds. 1607 "age" (string): The time that this order has been active in human readable form. 1608 "rate" (int): The exchange rate limit. Limit orders only. Units: quote 1609 asset per unit base asset. 1610 "quantity" (int): The amount being traded. 1611 "filled" (int): The order quantity that has matched. 1612 "settled" (int): The sum quantity of all completed matches. 1613 "status" (string): The status of the order. "epoch", "booked", "executed", 1614 "canceled", or "revoked". 1615 "cancelling" (bool): Whether this order is in the process of cancelling. 1616 "canceled" (bool): Whether this order has been canceled. 1617 "tif" (string): "immediate" if this limit order will only match for one epoch. 1618 "standing" if the order can continue matching until filled or cancelled. 1619 "matches": (array): An array of matches associated with the order. 1620 [ 1621 { 1622 "matchID (string): The match's ID." 1623 "status" (string): The match's status." 1624 "revoked" (bool): Indicates if match was revoked. 1625 "rate" (int): The match's rate. 1626 "qty" (int): The match's amount. 1627 "side" (string): The match's side, "maker" or "taker". 1628 "feerate" (int): The match's fee rate. 1629 "swap" (string): The match's swap transaction. 1630 "counterSwap" (string): The match's counter swap transaction. 1631 "redeem" (string): The match's redeem transaction. 1632 "counterRedeem" (string): The match's counter redeem transaction. 1633 "refund" (string): The match's refund transaction. 1634 "stamp" (int): The match's stamp. 1635 "isCancel" (bool): Indicates if match is canceled. 1636 },... 1637 ] 1638 },... 1639 ]`, 1640 }, 1641 appSeedRoute: { 1642 pwArgsShort: `"appPass"`, 1643 cmdSummary: `Show the application's seed. It is recommended to not store the seed 1644 digitally. Make a copy on paper with pencil and keep it safe.`, 1645 pwArgsLong: `Password Args: 1646 appPass (string): The Bison Wallet password.`, 1647 returns: `Returns: 1648 string: The application's seed as hex.`, 1649 }, 1650 walletPeersRoute: { 1651 cmdSummary: `Show the peers a wallet is connected to.`, 1652 argsShort: `(assetID)`, 1653 argsLong: `Args: 1654 assetID (int): The asset's BIP-44 registered coin index. Used to identify 1655 which wallet's peers to return.`, 1656 returns: `Returns: 1657 []string: Addresses of wallet peers.`, 1658 }, 1659 addWalletPeerRoute: { 1660 cmdSummary: `Add a new wallet peer connection.`, 1661 argsShort: `(assetID) (addr)`, 1662 argsLong: `Args: 1663 assetID (int): The asset's BIP-44 registered coin index. Used to identify 1664 which wallet to add a peer. 1665 addr (string): The peer's address (host:port).`, 1666 }, 1667 removeWalletPeerRoute: { 1668 cmdSummary: `Remove an added wallet peer.`, 1669 argsShort: `(assetID) (addr)`, 1670 argsLong: `Args: 1671 assetID (int): The asset's BIP-44 registered coin index. Used to identify 1672 which wallet to add a peer. 1673 addr (string): The peer's address (host:port).`, 1674 }, 1675 notificationsRoute: { 1676 cmdSummary: `See recent notifications.`, 1677 argsShort: `(num)`, 1678 argsLong: `Args: 1679 num (int): The number of notifications to load.`, 1680 }, 1681 startBotRoute: { 1682 cmdSummary: `Start market making.`, 1683 argsShort: `(cfgPath) (host) (baseID) (quoteID) (dexBals) (dexBals)`, 1684 argsLong: `Args: 1685 cfgPath (string): The path to the market maker config file. 1686 host (string): The DEX address. 1687 baseID (int): The base asset's BIP-44 registered coin index. 1688 quoteID (int): The quote asset's BIP-44 registered coin index. 1689 dexBalances (array): The DEX balances i.e. [[60,1000000],[42,10000000]]. 1690 cexBalances (array): The CEX balances i.e. [[60,1000000],[42,10000000]].`, 1691 }, 1692 stopBotRoute: { 1693 cmdSummary: `Stop market making.`, 1694 argsShort: `(host) (baseID) (quoteID)`, 1695 argsLong: `Args: 1696 host (string): The DEX address. 1697 baseID (int): The base asset's BIP-44 registered coin index. 1698 quoteID (int): The quote asset's BIP-44 registered coin index.`, 1699 }, 1700 mmAvailableBalancesRoute: { 1701 cmdSummary: `Get available balances for starting a bot or adding additional balance to a running bot.`, 1702 argsShort: `(cfgPath) (host) (baseID) (quoteID)`, 1703 argsLong: `Args: 1704 cfgPath (string): The path to the market maker config file. 1705 host (string): The DEX address. 1706 baseID (int): The base asset's BIP-44 registered coin index. 1707 quoteID (int): The quote asset's BIP-44 registered coin index.`, 1708 }, 1709 mmStatusRoute: { 1710 cmdSummary: `Get market making status.`, 1711 }, 1712 updateRunningBotCfgRoute: { 1713 cmdSummary: `Update the config and optionally the inventory of a running bot`, 1714 argsShort: `(cfgPath) (host) (baseID) (quoteID) (dexInventory) (cexInventory)`, 1715 argsLong: `Args: 1716 cfgPath (string): The path to the market maker config file. 1717 host (string): The DEX address. 1718 baseID (int): The base asset's BIP-44 registered coin index. 1719 quoteID (int): The quote asset's BIP-44 registered coin index. 1720 dexInventory (obj): (optional) The DEX inventory adjustments i.e. [[60,-1000000],[42,10000000]]. 1721 cexInventory (obj): (optional) The CEX inventory adjustments i.e. [[60,-1000000],[42,10000000]].`, 1722 }, 1723 updateRunningBotInvRoute: { 1724 cmdSummary: `Update the inventory of a running bot`, 1725 argsShort: `(host) (baseID) (quoteID) (dexInventory) (cexInventory)`, 1726 argsLong: `Args: 1727 host (string): The DEX address. 1728 baseID (int): The base asset's BIP-44 registered coin index. 1729 quoteID (int): The quote asset's BIP-44 registered coin index. 1730 dexInventory (obj): The DEX inventory adjustments i.e. [[60,-1000000],[42,10000000]]. 1731 cexInventory (obj): The CEX inventory adjustments i.e. [[60,-1000000],[42,10000000]].`, 1732 }, 1733 stakeStatusRoute: { 1734 cmdSummary: `Get stake status. `, 1735 argsShort: `assetID`, 1736 argsLong: `Args: 1737 assetID (int): The asset's BIP-44 registered coin index.`, 1738 returns: `Returns: 1739 obj: The staking status. 1740 { 1741 ticketPrice (uint64): The current ticket price in atoms. 1742 vsp (string): The url of the currently set vsp (voting service provider). 1743 isRPC (bool): Whether the wallet is an RPC wallet. False indicates 1744 an spv wallet and enables options to view and set the vsp. 1745 tickets (array): An array of ticket objects. 1746 [ 1747 { 1748 tx (obj): Ticket transaction data. 1749 { 1750 hash (string): The ticket hash as hex. 1751 ticketPrice (int): The amount paid for the ticket in atoms. 1752 fees (int): The ticket transaction's tx fee. 1753 stamp (int): The UNIX time the ticket was purchased. 1754 blockHeight (int): The block number the ticket was mined. 1755 }, 1756 status: (int) The ticket status. 0: unknown, 1: unmined, 2: immature, 3: live, 1757 4: voted, 5: missed, 6:expired, 7: unspent, 8: revoked. 1758 spender (string): The transaction that votes on or revokes the ticket if available. 1759 }, 1760 ],... 1761 stances (obj): Voting policies. 1762 { 1763 agendas (array): An array of consensus vote choices. 1764 [ 1765 { 1766 id (string): The agenda ID, 1767 description (string): A description of the agenda being voted on. 1768 currentChoice (string): Your current choice. 1769 choices ([{id: "string", description: "string"}, ...]): A description of the available choices. 1770 }, 1771 ],... 1772 tspends (array): An array of TSpend policies. 1773 [ 1774 { 1775 hash (string): The TSpend txid., 1776 value (int): The total value send in the tspend., 1777 currentValue (string): The policy. 1778 }, 1779 ],... 1780 treasuryKeys (array): An array of treasury policies. 1781 [ 1782 { 1783 key (string): The pubkey of the tspend creator. 1784 policy (string): The policy. 1785 }, 1786 ],... 1787 } 1788 }`, 1789 }, 1790 setVSPRoute: { 1791 argsShort: `assetID "addr"`, 1792 cmdSummary: `Set a vsp by url.`, 1793 argsLong: `Args: 1794 assetID (int): The asset's BIP-44 registered coin index. 1795 addr (string): The vsp's url.`, 1796 returns: `Returns: 1797 string: The message "` + fmt.Sprintf(setVSPStr, "[vsp url]") + `"`, 1798 }, 1799 purchaseTicketsRoute: { 1800 pwArgsShort: `"appPass"`, 1801 argsShort: `assetID num`, 1802 cmdSummary: `Starts a asyncrhonous ticket purchasing process. Check stakestatus for number of tickets remaining to be purchased.`, 1803 pwArgsLong: `Password Args: 1804 appPass (string): The Bison Wallet password.`, 1805 argsLong: `Args: 1806 assetID (int): The asset's BIP-44 registered coin index. 1807 num (int): The number of tickets to purchase`, 1808 returns: `Returns: 1809 bool: true is the only non-error return value`, 1810 }, 1811 setVotingPreferencesRoute: { 1812 argsShort: `assetID (choicesMap) (tSpendPolicyMap) (treasuryPolicyMap)`, 1813 cmdSummary: `Cancel an order.`, 1814 argsLong: `Args: 1815 assetID (int): The asset's BIP-44 registered coin index. 1816 choicesMap ({"agendaid": "choiceid", ...}): A map of choices IDs to choice policies. 1817 tSpendPolicyMap ({"hash": "policy", ...}): A map of tSpend txids to tSpend policies. 1818 treasuryPolicyMap ({"key": "policy", ...}): A map of treasury spender public keys to tSpend policies.`, 1819 returns: `Returns: 1820 string: The message "` + setVotePrefsStr + `"`, 1821 }, 1822 txHistoryRoute: { 1823 argsShort: `assetID (n) (refTxID) (past)`, 1824 cmdSummary: `Get transaction history for a wallet`, 1825 argsLong: `Args: 1826 assetID (int): The asset's BIP-44 registered coin index. 1827 n (int): Optional. The number of transactions to return. If <= 0 or unset, all transactions are returned. 1828 refTxID (string): Optional. If set, the transactions before or after this tx (depending on the past argument) 1829 will be returned. 1830 past (bool): If true, the transactions before the reference tx will be returned. If false, the 1831 transactions after the reference tx will be returned.`, 1832 }, 1833 walletTxRoute: { 1834 argsShort: `assetID txID`, 1835 cmdSummary: `Get a wallet transaction`, 1836 argsLong: `Args: 1837 assetID (int): The asset's BIP-44 registered coin index. 1838 txID (string): The transaction ID.`, 1839 }, 1840 withdrawBchSpvRoute: { 1841 pwArgsShort: `"appPass"`, 1842 argsShort: `recipient`, 1843 cmdSummary: `Get a transaction that will withdraw all funds from the deprecated Bitcoin Cash SPV wallet`, 1844 argsLong: `Args: 1845 recipient (string): The Bitcoin Cash address to withdraw the funds to`, 1846 }, 1847 }