gitlab.com/jokerrs1/Sia@v1.3.2/node/api/wallet.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "net/http" 6 "path/filepath" 7 "strconv" 8 "strings" 9 10 "github.com/NebulousLabs/Sia/crypto" 11 "github.com/NebulousLabs/Sia/modules" 12 "github.com/NebulousLabs/Sia/types" 13 14 "github.com/NebulousLabs/entropy-mnemonics" 15 "github.com/julienschmidt/httprouter" 16 ) 17 18 type ( 19 // WalletGET contains general information about the wallet. 20 WalletGET struct { 21 Encrypted bool `json:"encrypted"` 22 Height types.BlockHeight `json:"height"` 23 Rescanning bool `json:"rescanning"` 24 Unlocked bool `json:"unlocked"` 25 26 ConfirmedSiacoinBalance types.Currency `json:"confirmedsiacoinbalance"` 27 UnconfirmedOutgoingSiacoins types.Currency `json:"unconfirmedoutgoingsiacoins"` 28 UnconfirmedIncomingSiacoins types.Currency `json:"unconfirmedincomingsiacoins"` 29 30 SiacoinClaimBalance types.Currency `json:"siacoinclaimbalance"` 31 SiafundBalance types.Currency `json:"siafundbalance"` 32 33 DustThreshold types.Currency `json:"dustthreshold"` 34 } 35 36 // WalletAddressGET contains an address returned by a GET call to 37 // /wallet/address. 38 WalletAddressGET struct { 39 Address types.UnlockHash `json:"address"` 40 } 41 42 // WalletAddressesGET contains the list of wallet addresses returned by a 43 // GET call to /wallet/addresses. 44 WalletAddressesGET struct { 45 Addresses []types.UnlockHash `json:"addresses"` 46 } 47 48 // WalletInitPOST contains the primary seed that gets generated during a 49 // POST call to /wallet/init. 50 WalletInitPOST struct { 51 PrimarySeed string `json:"primaryseed"` 52 } 53 54 // WalletSiacoinsPOST contains the transaction sent in the POST call to 55 // /wallet/siacoins. 56 WalletSiacoinsPOST struct { 57 TransactionIDs []types.TransactionID `json:"transactionids"` 58 } 59 60 // WalletSiafundsPOST contains the transaction sent in the POST call to 61 // /wallet/siafunds. 62 WalletSiafundsPOST struct { 63 TransactionIDs []types.TransactionID `json:"transactionids"` 64 } 65 66 // WalletSeedsGET contains the seeds used by the wallet. 67 WalletSeedsGET struct { 68 PrimarySeed string `json:"primaryseed"` 69 AddressesRemaining int `json:"addressesremaining"` 70 AllSeeds []string `json:"allseeds"` 71 } 72 73 // WalletSweepPOST contains the coins and funds returned by a call to 74 // /wallet/sweep. 75 WalletSweepPOST struct { 76 Coins types.Currency `json:"coins"` 77 Funds types.Currency `json:"funds"` 78 } 79 80 // WalletTransactionGETid contains the transaction returned by a call to 81 // /wallet/transaction/:id 82 WalletTransactionGETid struct { 83 Transaction modules.ProcessedTransaction `json:"transaction"` 84 } 85 86 // WalletTransactionsGET contains the specified set of confirmed and 87 // unconfirmed transactions. 88 WalletTransactionsGET struct { 89 ConfirmedTransactions []modules.ProcessedTransaction `json:"confirmedtransactions"` 90 UnconfirmedTransactions []modules.ProcessedTransaction `json:"unconfirmedtransactions"` 91 } 92 93 // WalletTransactionsGETaddr contains the set of wallet transactions 94 // relevant to the input address provided in the call to 95 // /wallet/transaction/:addr 96 WalletTransactionsGETaddr struct { 97 ConfirmedTransactions []modules.ProcessedTransaction `json:"confirmedtransactions"` 98 UnconfirmedTransactions []modules.ProcessedTransaction `json:"unconfirmedtransactions"` 99 } 100 101 // WalletVerifyAddressGET contains a bool indicating if the address passed to 102 // /wallet/verify/address/:addr is a valid address. 103 WalletVerifyAddressGET struct { 104 Valid bool `json:"valid"` 105 } 106 ) 107 108 // encryptionKeys enumerates the possible encryption keys that can be derived 109 // from an input string. 110 func encryptionKeys(seedStr string) (validKeys []crypto.TwofishKey) { 111 dicts := []mnemonics.DictionaryID{"english", "german", "japanese"} 112 for _, dict := range dicts { 113 seed, err := modules.StringToSeed(seedStr, dict) 114 if err != nil { 115 continue 116 } 117 validKeys = append(validKeys, crypto.TwofishKey(crypto.HashObject(seed))) 118 } 119 validKeys = append(validKeys, crypto.TwofishKey(crypto.HashObject(seedStr))) 120 return validKeys 121 } 122 123 // walletHander handles API calls to /wallet. 124 func (api *API) walletHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 125 siacoinBal, siafundBal, siaclaimBal := api.wallet.ConfirmedBalance() 126 siacoinsOut, siacoinsIn := api.wallet.UnconfirmedBalance() 127 dustThreshold := api.wallet.DustThreshold() 128 WriteJSON(w, WalletGET{ 129 Encrypted: api.wallet.Encrypted(), 130 Unlocked: api.wallet.Unlocked(), 131 Rescanning: api.wallet.Rescanning(), 132 Height: api.wallet.Height(), 133 134 ConfirmedSiacoinBalance: siacoinBal, 135 UnconfirmedOutgoingSiacoins: siacoinsOut, 136 UnconfirmedIncomingSiacoins: siacoinsIn, 137 138 SiafundBalance: siafundBal, 139 SiacoinClaimBalance: siaclaimBal, 140 141 DustThreshold: dustThreshold, 142 }) 143 } 144 145 // wallet033xHandler handles API calls to /wallet/033x. 146 func (api *API) wallet033xHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 147 source := req.FormValue("source") 148 // Check that source is an absolute paths. 149 if !filepath.IsAbs(source) { 150 WriteError(w, Error{"error when calling /wallet/033x: source must be an absolute path"}, http.StatusBadRequest) 151 return 152 } 153 potentialKeys := encryptionKeys(req.FormValue("encryptionpassword")) 154 for _, key := range potentialKeys { 155 err := api.wallet.Load033xWallet(key, source) 156 if err == nil { 157 WriteSuccess(w) 158 return 159 } 160 if err != nil && err != modules.ErrBadEncryptionKey { 161 WriteError(w, Error{"error when calling /wallet/033x: " + err.Error()}, http.StatusBadRequest) 162 return 163 } 164 } 165 WriteError(w, Error{modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 166 } 167 168 // walletAddressHandler handles API calls to /wallet/address. 169 func (api *API) walletAddressHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 170 unlockConditions, err := api.wallet.NextAddress() 171 if err != nil { 172 WriteError(w, Error{"error when calling /wallet/addresses: " + err.Error()}, http.StatusBadRequest) 173 return 174 } 175 WriteJSON(w, WalletAddressGET{ 176 Address: unlockConditions.UnlockHash(), 177 }) 178 } 179 180 // walletAddressHandler handles API calls to /wallet/addresses. 181 func (api *API) walletAddressesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 182 WriteJSON(w, WalletAddressesGET{ 183 Addresses: api.wallet.AllAddresses(), 184 }) 185 } 186 187 // walletBackupHandler handles API calls to /wallet/backup. 188 func (api *API) walletBackupHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 189 destination := req.FormValue("destination") 190 // Check that the destination is absolute. 191 if !filepath.IsAbs(destination) { 192 WriteError(w, Error{"error when calling /wallet/backup: destination must be an absolute path"}, http.StatusBadRequest) 193 return 194 } 195 err := api.wallet.CreateBackup(destination) 196 if err != nil { 197 WriteError(w, Error{"error when calling /wallet/backup: " + err.Error()}, http.StatusBadRequest) 198 return 199 } 200 WriteSuccess(w) 201 } 202 203 // walletInitHandler handles API calls to /wallet/init. 204 func (api *API) walletInitHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 205 var encryptionKey crypto.TwofishKey 206 if req.FormValue("encryptionpassword") != "" { 207 encryptionKey = crypto.TwofishKey(crypto.HashObject(req.FormValue("encryptionpassword"))) 208 } 209 210 if req.FormValue("force") == "true" { 211 err := api.wallet.Reset() 212 if err != nil { 213 WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest) 214 return 215 } 216 } 217 seed, err := api.wallet.Encrypt(encryptionKey) 218 if err != nil { 219 WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest) 220 return 221 } 222 223 dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) 224 if dictID == "" { 225 dictID = "english" 226 } 227 seedStr, err := modules.SeedToString(seed, dictID) 228 if err != nil { 229 WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest) 230 return 231 } 232 WriteJSON(w, WalletInitPOST{ 233 PrimarySeed: seedStr, 234 }) 235 } 236 237 // walletInitSeedHandler handles API calls to /wallet/init/seed. 238 func (api *API) walletInitSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 239 var encryptionKey crypto.TwofishKey 240 if req.FormValue("encryptionpassword") != "" { 241 encryptionKey = crypto.TwofishKey(crypto.HashObject(req.FormValue("encryptionpassword"))) 242 } 243 dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) 244 if dictID == "" { 245 dictID = "english" 246 } 247 seed, err := modules.StringToSeed(req.FormValue("seed"), dictID) 248 if err != nil { 249 WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest) 250 return 251 } 252 253 if req.FormValue("force") == "true" { 254 err = api.wallet.Reset() 255 if err != nil { 256 WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest) 257 return 258 } 259 } 260 261 err = api.wallet.InitFromSeed(encryptionKey, seed) 262 if err != nil { 263 WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest) 264 return 265 } 266 WriteSuccess(w) 267 } 268 269 // walletSeedHandler handles API calls to /wallet/seed. 270 func (api *API) walletSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 271 // Get the seed using the ditionary + phrase 272 dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) 273 if dictID == "" { 274 dictID = "english" 275 } 276 seed, err := modules.StringToSeed(req.FormValue("seed"), dictID) 277 if err != nil { 278 WriteError(w, Error{"error when calling /wallet/seed: " + err.Error()}, http.StatusBadRequest) 279 return 280 } 281 282 potentialKeys := encryptionKeys(req.FormValue("encryptionpassword")) 283 for _, key := range potentialKeys { 284 err := api.wallet.LoadSeed(key, seed) 285 if err == nil { 286 WriteSuccess(w) 287 return 288 } 289 if err != nil && err != modules.ErrBadEncryptionKey { 290 WriteError(w, Error{"error when calling /wallet/seed: " + err.Error()}, http.StatusBadRequest) 291 return 292 } 293 } 294 WriteError(w, Error{"error when calling /wallet/seed: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 295 } 296 297 // walletSiagkeyHandler handles API calls to /wallet/siagkey. 298 func (api *API) walletSiagkeyHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 299 // Fetch the list of keyfiles from the post body. 300 keyfiles := strings.Split(req.FormValue("keyfiles"), ",") 301 potentialKeys := encryptionKeys(req.FormValue("encryptionpassword")) 302 303 for _, keypath := range keyfiles { 304 // Check that all key paths are absolute paths. 305 if !filepath.IsAbs(keypath) { 306 WriteError(w, Error{"error when calling /wallet/siagkey: keyfiles contains a non-absolute path"}, http.StatusBadRequest) 307 return 308 } 309 } 310 311 for _, key := range potentialKeys { 312 err := api.wallet.LoadSiagKeys(key, keyfiles) 313 if err == nil { 314 WriteSuccess(w) 315 return 316 } 317 if err != nil && err != modules.ErrBadEncryptionKey { 318 WriteError(w, Error{"error when calling /wallet/siagkey: " + err.Error()}, http.StatusBadRequest) 319 return 320 } 321 } 322 WriteError(w, Error{"error when calling /wallet/siagkey: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 323 } 324 325 // walletLockHanlder handles API calls to /wallet/lock. 326 func (api *API) walletLockHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 327 err := api.wallet.Lock() 328 if err != nil { 329 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 330 return 331 } 332 WriteSuccess(w) 333 } 334 335 // walletSeedsHandler handles API calls to /wallet/seeds. 336 func (api *API) walletSeedsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 337 dictionary := mnemonics.DictionaryID(req.FormValue("dictionary")) 338 if dictionary == "" { 339 dictionary = mnemonics.English 340 } 341 342 // Get the primary seed information. 343 primarySeed, addrsRemaining, err := api.wallet.PrimarySeed() 344 if err != nil { 345 WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest) 346 return 347 } 348 primarySeedStr, err := modules.SeedToString(primarySeed, dictionary) 349 if err != nil { 350 WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest) 351 return 352 } 353 354 // Get the list of seeds known to the wallet. 355 allSeeds, err := api.wallet.AllSeeds() 356 if err != nil { 357 WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest) 358 return 359 } 360 var allSeedsStrs []string 361 for _, seed := range allSeeds { 362 str, err := modules.SeedToString(seed, dictionary) 363 if err != nil { 364 WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest) 365 return 366 } 367 allSeedsStrs = append(allSeedsStrs, str) 368 } 369 WriteJSON(w, WalletSeedsGET{ 370 PrimarySeed: primarySeedStr, 371 AddressesRemaining: int(addrsRemaining), 372 AllSeeds: allSeedsStrs, 373 }) 374 } 375 376 // walletSiacoinsHandler handles API calls to /wallet/siacoins. 377 func (api *API) walletSiacoinsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 378 var txns []types.Transaction 379 if req.FormValue("outputs") != "" { 380 // multiple amounts + destinations 381 if req.FormValue("amount") != "" || req.FormValue("destination") != "" { 382 WriteError(w, Error{"cannot supply both 'outputs' and single amount+destination pair"}, http.StatusInternalServerError) 383 return 384 } 385 386 var outputs []types.SiacoinOutput 387 err := json.Unmarshal([]byte(req.FormValue("outputs")), &outputs) 388 if err != nil { 389 WriteError(w, Error{"could not decode outputs: " + err.Error()}, http.StatusInternalServerError) 390 return 391 } 392 txns, err = api.wallet.SendSiacoinsMulti(outputs) 393 if err != nil { 394 WriteError(w, Error{"error when calling /wallet/siacoins: " + err.Error()}, http.StatusInternalServerError) 395 return 396 } 397 } else { 398 // single amount + destination 399 amount, ok := scanAmount(req.FormValue("amount")) 400 if !ok { 401 WriteError(w, Error{"could not read amount from POST call to /wallet/siacoins"}, http.StatusBadRequest) 402 return 403 } 404 dest, err := scanAddress(req.FormValue("destination")) 405 if err != nil { 406 WriteError(w, Error{"could not read address from POST call to /wallet/siacoins"}, http.StatusBadRequest) 407 return 408 } 409 410 txns, err = api.wallet.SendSiacoins(amount, dest) 411 if err != nil { 412 WriteError(w, Error{"error when calling /wallet/siacoins: " + err.Error()}, http.StatusInternalServerError) 413 return 414 } 415 416 } 417 418 var txids []types.TransactionID 419 for _, txn := range txns { 420 txids = append(txids, txn.ID()) 421 } 422 WriteJSON(w, WalletSiacoinsPOST{ 423 TransactionIDs: txids, 424 }) 425 } 426 427 // walletSiafundsHandler handles API calls to /wallet/siafunds. 428 func (api *API) walletSiafundsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 429 amount, ok := scanAmount(req.FormValue("amount")) 430 if !ok { 431 WriteError(w, Error{"could not read 'amount' from POST call to /wallet/siafunds"}, http.StatusBadRequest) 432 return 433 } 434 dest, err := scanAddress(req.FormValue("destination")) 435 if err != nil { 436 WriteError(w, Error{"error when calling /wallet/siafunds: " + err.Error()}, http.StatusBadRequest) 437 return 438 } 439 440 txns, err := api.wallet.SendSiafunds(amount, dest) 441 if err != nil { 442 WriteError(w, Error{"error when calling /wallet/siafunds: " + err.Error()}, http.StatusInternalServerError) 443 return 444 } 445 var txids []types.TransactionID 446 for _, txn := range txns { 447 txids = append(txids, txn.ID()) 448 } 449 WriteJSON(w, WalletSiafundsPOST{ 450 TransactionIDs: txids, 451 }) 452 } 453 454 // walletSweepSeedHandler handles API calls to /wallet/sweep/seed. 455 func (api *API) walletSweepSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 456 // Get the seed using the ditionary + phrase 457 dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) 458 if dictID == "" { 459 dictID = "english" 460 } 461 seed, err := modules.StringToSeed(req.FormValue("seed"), dictID) 462 if err != nil { 463 WriteError(w, Error{"error when calling /wallet/sweep/seed: " + err.Error()}, http.StatusBadRequest) 464 return 465 } 466 467 coins, funds, err := api.wallet.SweepSeed(seed) 468 if err != nil { 469 WriteError(w, Error{"error when calling /wallet/sweep/seed: " + err.Error()}, http.StatusBadRequest) 470 return 471 } 472 WriteJSON(w, WalletSweepPOST{ 473 Coins: coins, 474 Funds: funds, 475 }) 476 } 477 478 // walletTransactionHandler handles API calls to /wallet/transaction/:id. 479 func (api *API) walletTransactionHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 480 // Parse the id from the url. 481 var id types.TransactionID 482 jsonID := "\"" + ps.ByName("id") + "\"" 483 err := id.UnmarshalJSON([]byte(jsonID)) 484 if err != nil { 485 WriteError(w, Error{"error when calling /wallet/history: " + err.Error()}, http.StatusBadRequest) 486 return 487 } 488 489 txn, ok := api.wallet.Transaction(id) 490 if !ok { 491 WriteError(w, Error{"error when calling /wallet/transaction/:id : transaction not found"}, http.StatusBadRequest) 492 return 493 } 494 WriteJSON(w, WalletTransactionGETid{ 495 Transaction: txn, 496 }) 497 } 498 499 // walletTransactionsHandler handles API calls to /wallet/transactions. 500 func (api *API) walletTransactionsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 501 startheightStr, endheightStr := req.FormValue("startheight"), req.FormValue("endheight") 502 if startheightStr == "" || endheightStr == "" { 503 WriteError(w, Error{"startheight and endheight must be provided to a /wallet/transactions call."}, http.StatusBadRequest) 504 return 505 } 506 // Get the start and end blocks. 507 start, err := strconv.Atoi(startheightStr) 508 if err != nil { 509 WriteError(w, Error{"parsing integer value for parameter `startheight` failed: " + err.Error()}, http.StatusBadRequest) 510 return 511 } 512 end, err := strconv.Atoi(endheightStr) 513 if err != nil { 514 WriteError(w, Error{"parsing integer value for parameter `endheight` failed: " + err.Error()}, http.StatusBadRequest) 515 return 516 } 517 confirmedTxns, err := api.wallet.Transactions(types.BlockHeight(start), types.BlockHeight(end)) 518 if err != nil { 519 WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest) 520 return 521 } 522 unconfirmedTxns := api.wallet.UnconfirmedTransactions() 523 524 WriteJSON(w, WalletTransactionsGET{ 525 ConfirmedTransactions: confirmedTxns, 526 UnconfirmedTransactions: unconfirmedTxns, 527 }) 528 } 529 530 // walletTransactionsAddrHandler handles API calls to 531 // /wallet/transactions/:addr. 532 func (api *API) walletTransactionsAddrHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 533 // Parse the address being input. 534 jsonAddr := "\"" + ps.ByName("addr") + "\"" 535 var addr types.UnlockHash 536 err := addr.UnmarshalJSON([]byte(jsonAddr)) 537 if err != nil { 538 WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest) 539 return 540 } 541 542 confirmedATs := api.wallet.AddressTransactions(addr) 543 unconfirmedATs := api.wallet.AddressUnconfirmedTransactions(addr) 544 WriteJSON(w, WalletTransactionsGETaddr{ 545 ConfirmedTransactions: confirmedATs, 546 UnconfirmedTransactions: unconfirmedATs, 547 }) 548 } 549 550 // walletUnlockHandler handles API calls to /wallet/unlock. 551 func (api *API) walletUnlockHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 552 potentialKeys := encryptionKeys(req.FormValue("encryptionpassword")) 553 for _, key := range potentialKeys { 554 err := api.wallet.Unlock(key) 555 if err == nil { 556 WriteSuccess(w) 557 return 558 } 559 if err != nil && err != modules.ErrBadEncryptionKey { 560 WriteError(w, Error{"error when calling /wallet/unlock: " + err.Error()}, http.StatusBadRequest) 561 return 562 } 563 } 564 WriteError(w, Error{"error when calling /wallet/unlock: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 565 } 566 567 // walletChangePasswordHandler handles API calls to /wallet/changepassword 568 func (api *API) walletChangePasswordHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 569 var newKey crypto.TwofishKey 570 newPassword := req.FormValue("newpassword") 571 if newPassword == "" { 572 WriteError(w, Error{"a password must be provided to newpassword"}, http.StatusBadRequest) 573 return 574 } 575 newKey = crypto.TwofishKey(crypto.HashObject(newPassword)) 576 577 originalKeys := encryptionKeys(req.FormValue("encryptionpassword")) 578 for _, key := range originalKeys { 579 err := api.wallet.ChangeKey(key, newKey) 580 if err == nil { 581 WriteSuccess(w) 582 return 583 } 584 if err != nil && err != modules.ErrBadEncryptionKey { 585 WriteError(w, Error{"error when calling /wallet/changepassword: " + err.Error()}, http.StatusBadRequest) 586 return 587 } 588 } 589 WriteError(w, Error{"error when calling /wallet/changepassword: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 590 } 591 592 // walletVerifyAddressHandler handles API calls to /wallet/verify/address/:addr. 593 func (api *API) walletVerifyAddressHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 594 addrString := ps.ByName("addr") 595 596 err := new(types.UnlockHash).LoadString(addrString) 597 WriteJSON(w, WalletVerifyAddressGET{Valid: err == nil}) 598 }