gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/wallet.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "math" 7 "net/http" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 "gitlab.com/SiaPrime/SiaPrime/crypto" 13 "gitlab.com/SiaPrime/SiaPrime/modules" 14 "gitlab.com/SiaPrime/SiaPrime/types" 15 16 "github.com/julienschmidt/httprouter" 17 mnemonics "gitlab.com/NebulousLabs/entropy-mnemonics" 18 ) 19 20 type ( 21 // WalletGET contains general information about the wallet. 22 WalletGET struct { 23 Encrypted bool `json:"encrypted"` 24 Height types.BlockHeight `json:"height"` 25 Rescanning bool `json:"rescanning"` 26 Unlocked bool `json:"unlocked"` 27 28 ConfirmedSiacoinBalance types.Currency `json:"confirmedsiacoinbalance"` 29 UnconfirmedOutgoingSiacoins types.Currency `json:"unconfirmedoutgoingsiacoins"` 30 UnconfirmedIncomingSiacoins types.Currency `json:"unconfirmedincomingsiacoins"` 31 32 SiacoinClaimBalance types.Currency `json:"siacoinclaimbalance"` 33 SiafundBalance types.Currency `json:"siafundbalance"` 34 35 DustThreshold types.Currency `json:"dustthreshold"` 36 } 37 38 // WalletAddressGET contains an address returned by a GET call to 39 // /wallet/address. 40 WalletAddressGET struct { 41 Address types.UnlockHash `json:"address"` 42 } 43 44 // WalletAddressesGET contains the list of wallet addresses returned by a 45 // GET call to /wallet/addresses. 46 WalletAddressesGET struct { 47 Addresses []types.UnlockHash `json:"addresses"` 48 } 49 50 // WalletInitPOST contains the primary seed that gets generated during a 51 // POST call to /wallet/init. 52 WalletInitPOST struct { 53 PrimarySeed string `json:"primaryseed"` 54 } 55 56 // WalletSiacoinsPOST contains the transaction sent in the POST call to 57 // /wallet/siacoins. 58 WalletSiacoinsPOST struct { 59 TransactionIDs []types.TransactionID `json:"transactionids"` 60 } 61 62 // WalletSiafundsPOST contains the transaction sent in the POST call to 63 // /wallet/siafunds. 64 WalletSiafundsPOST struct { 65 TransactionIDs []types.TransactionID `json:"transactionids"` 66 } 67 68 // WalletSignPOSTParams contains the unsigned transaction and a set of 69 // inputs to sign. 70 WalletSignPOSTParams struct { 71 Transaction types.Transaction `json:"transaction"` 72 ToSign []crypto.Hash `json:"tosign"` 73 } 74 75 // WalletSignPOSTResp contains the signed transaction. 76 WalletSignPOSTResp struct { 77 Transaction types.Transaction `json:"transaction"` 78 } 79 80 // WalletSeedsGET contains the seeds used by the wallet. 81 WalletSeedsGET struct { 82 PrimarySeed string `json:"primaryseed"` 83 AddressesRemaining int `json:"addressesremaining"` 84 AllSeeds []string `json:"allseeds"` 85 } 86 87 // WalletSweepPOST contains the coins and funds returned by a call to 88 // /wallet/sweep. 89 WalletSweepPOST struct { 90 Coins types.Currency `json:"coins"` 91 Funds types.Currency `json:"funds"` 92 } 93 94 // WalletTransactionGETid contains the transaction returned by a call to 95 // /wallet/transaction/:id 96 WalletTransactionGETid struct { 97 Transaction modules.ProcessedTransaction `json:"transaction"` 98 } 99 100 // WalletTransactionsGET contains the specified set of confirmed and 101 // unconfirmed transactions. 102 WalletTransactionsGET struct { 103 ConfirmedTransactions []modules.ProcessedTransaction `json:"confirmedtransactions"` 104 UnconfirmedTransactions []modules.ProcessedTransaction `json:"unconfirmedtransactions"` 105 } 106 107 // WalletTransactionsGETaddr contains the set of wallet transactions 108 // relevant to the input address provided in the call to 109 // /wallet/transaction/:addr 110 WalletTransactionsGETaddr struct { 111 ConfirmedTransactions []modules.ProcessedTransaction `json:"confirmedtransactions"` 112 UnconfirmedTransactions []modules.ProcessedTransaction `json:"unconfirmedtransactions"` 113 } 114 115 // WalletUnlockConditionsGET contains a set of unlock conditions. 116 WalletUnlockConditionsGET struct { 117 UnlockConditions types.UnlockConditions `json:"unlockconditions"` 118 } 119 120 // WalletUnlockConditionsPOSTParams contains a set of unlock conditions. 121 WalletUnlockConditionsPOSTParams struct { 122 UnlockConditions types.UnlockConditions `json:"unlockconditions"` 123 } 124 125 // WalletUnspentGET contains the unspent outputs tracked by the wallet. 126 // The MaturityHeight field of each output indicates the height of the 127 // block that the output appeared in. 128 WalletUnspentGET struct { 129 Outputs []modules.UnspentOutput `json:"outputs"` 130 } 131 132 // WalletVerifyAddressGET contains a bool indicating if the address passed to 133 // /wallet/verify/address/:addr is a valid address. 134 WalletVerifyAddressGET struct { 135 Valid bool `json:"valid"` 136 } 137 138 // WalletWatchPOST contains the set of addresses to add or remove from the 139 // watch set. 140 WalletWatchPOST struct { 141 Addresses []types.UnlockHash `json:"addresses"` 142 Remove bool `json:"remove"` 143 Unused bool `json:"unused"` 144 } 145 146 // WalletWatchGET contains the set of addresses that the wallet is 147 // currently watching. 148 WalletWatchGET struct { 149 Addresses []types.UnlockHash `json:"addresses"` 150 } 151 ) 152 153 // encryptionKeys enumerates the possible encryption keys that can be derived 154 // from an input string. 155 func encryptionKeys(seedStr string) (validKeys []crypto.CipherKey) { 156 dicts := []mnemonics.DictionaryID{"english", "german", "japanese"} 157 for _, dict := range dicts { 158 seed, err := modules.StringToSeed(seedStr, dict) 159 if err != nil { 160 continue 161 } 162 validKeys = append(validKeys, crypto.NewWalletKey(crypto.HashObject(seed))) 163 } 164 validKeys = append(validKeys, crypto.NewWalletKey(crypto.HashObject(seedStr))) 165 return validKeys 166 } 167 168 // walletHander handles API calls to /wallet. 169 func (api *API) walletHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 170 siacoinBal, siafundBal, siaclaimBal, err := api.wallet.ConfirmedBalance() 171 if err != nil { 172 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest) 173 return 174 } 175 siacoinsOut, siacoinsIn, err := api.wallet.UnconfirmedBalance() 176 if err != nil { 177 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest) 178 return 179 } 180 dustThreshold, err := api.wallet.DustThreshold() 181 if err != nil { 182 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest) 183 return 184 } 185 encrypted, err := api.wallet.Encrypted() 186 if err != nil { 187 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest) 188 return 189 } 190 unlocked, err := api.wallet.Unlocked() 191 if err != nil { 192 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest) 193 return 194 } 195 rescanning, err := api.wallet.Rescanning() 196 if err != nil { 197 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest) 198 return 199 } 200 height, err := api.wallet.Height() 201 if err != nil { 202 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest) 203 return 204 } 205 WriteJSON(w, WalletGET{ 206 Encrypted: encrypted, 207 Unlocked: unlocked, 208 Rescanning: rescanning, 209 Height: height, 210 211 ConfirmedSiacoinBalance: siacoinBal, 212 UnconfirmedOutgoingSiacoins: siacoinsOut, 213 UnconfirmedIncomingSiacoins: siacoinsIn, 214 215 SiafundBalance: siafundBal, 216 SiacoinClaimBalance: siaclaimBal, 217 218 DustThreshold: dustThreshold, 219 }) 220 } 221 222 // wallet033xHandler handles API calls to /wallet/033x. 223 func (api *API) wallet033xHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 224 source := req.FormValue("source") 225 // Check that source is an absolute paths. 226 if !filepath.IsAbs(source) { 227 WriteError(w, Error{"error when calling /wallet/033x: source must be an absolute path"}, http.StatusBadRequest) 228 return 229 } 230 potentialKeys := encryptionKeys(req.FormValue("encryptionpassword")) 231 for _, key := range potentialKeys { 232 err := api.wallet.Load033xWallet(key, source) 233 if err == nil { 234 WriteSuccess(w) 235 return 236 } 237 if err != modules.ErrBadEncryptionKey { 238 WriteError(w, Error{"error when calling /wallet/033x: " + err.Error()}, http.StatusBadRequest) 239 return 240 } 241 } 242 WriteError(w, Error{modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 243 } 244 245 // walletAddressHandler handles API calls to /wallet/address. 246 func (api *API) walletAddressHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 247 unlockConditions, err := api.wallet.NextAddress() 248 if err != nil { 249 WriteError(w, Error{"error when calling /wallet/addresses: " + err.Error()}, http.StatusBadRequest) 250 return 251 } 252 WriteJSON(w, WalletAddressGET{ 253 Address: unlockConditions.UnlockHash(), 254 }) 255 } 256 257 // walletSeedAddressesHandler handles the requests to /wallet/seedaddrs. 258 func (api *API) walletSeedAddressesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 259 // Parse the count argument. If it isn't specified we return as many 260 // addresses as possible. 261 count := uint64(math.MaxUint64) 262 c := req.FormValue("count") 263 if c != "" { 264 _, err := fmt.Sscan(c, &count) 265 if err != nil { 266 WriteError(w, Error{"Failed to parse count: " + err.Error()}, http.StatusBadRequest) 267 return 268 } 269 } 270 // Get the last count addresses. 271 addresses, err := api.wallet.LastAddresses(count) 272 if err != nil { 273 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet/addresses: %v", err)}, http.StatusBadRequest) 274 return 275 } 276 // Send the response. 277 WriteJSON(w, WalletAddressesGET{ 278 Addresses: addresses, 279 }) 280 } 281 282 // walletAddressHandler handles API calls to /wallet/addresses. 283 func (api *API) walletAddressesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 284 addresses, err := api.wallet.AllAddresses() 285 if err != nil { 286 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet/addresses: %v", err)}, http.StatusBadRequest) 287 return 288 } 289 WriteJSON(w, WalletAddressesGET{ 290 Addresses: addresses, 291 }) 292 } 293 294 // walletBackupHandler handles API calls to /wallet/backup. 295 func (api *API) walletBackupHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 296 destination := req.FormValue("destination") 297 // Check that the destination is absolute. 298 if !filepath.IsAbs(destination) { 299 WriteError(w, Error{"error when calling /wallet/backup: destination must be an absolute path"}, http.StatusBadRequest) 300 return 301 } 302 err := api.wallet.CreateBackup(destination) 303 if err != nil { 304 WriteError(w, Error{"error when calling /wallet/backup: " + err.Error()}, http.StatusBadRequest) 305 return 306 } 307 WriteSuccess(w) 308 } 309 310 // walletInitHandler handles API calls to /wallet/init. 311 func (api *API) walletInitHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 312 var encryptionKey crypto.CipherKey 313 if req.FormValue("encryptionpassword") != "" { 314 encryptionKey = crypto.NewWalletKey(crypto.HashObject(req.FormValue("encryptionpassword"))) 315 } 316 317 if req.FormValue("force") == "true" { 318 err := api.wallet.Reset() 319 if err != nil { 320 WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest) 321 return 322 } 323 } 324 seed, err := api.wallet.Encrypt(encryptionKey) 325 if err != nil { 326 WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest) 327 return 328 } 329 330 dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) 331 if dictID == "" { 332 dictID = "english" 333 } 334 seedStr, err := modules.SeedToString(seed, dictID) 335 if err != nil { 336 WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest) 337 return 338 } 339 WriteJSON(w, WalletInitPOST{ 340 PrimarySeed: seedStr, 341 }) 342 } 343 344 // walletInitSeedHandler handles API calls to /wallet/init/seed. 345 func (api *API) walletInitSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 346 var encryptionKey crypto.CipherKey 347 if req.FormValue("encryptionpassword") != "" { 348 encryptionKey = crypto.NewWalletKey(crypto.HashObject(req.FormValue("encryptionpassword"))) 349 } 350 dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) 351 if dictID == "" { 352 dictID = "english" 353 } 354 seed, err := modules.StringToSeed(req.FormValue("seed"), dictID) 355 if err != nil { 356 WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest) 357 return 358 } 359 360 if req.FormValue("force") == "true" { 361 err = api.wallet.Reset() 362 if err != nil { 363 WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest) 364 return 365 } 366 } 367 368 err = api.wallet.InitFromSeed(encryptionKey, seed) 369 if err != nil { 370 WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest) 371 return 372 } 373 WriteSuccess(w) 374 } 375 376 // walletSeedHandler handles API calls to /wallet/seed. 377 func (api *API) walletSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 378 // Get the seed using the ditionary + phrase 379 dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) 380 if dictID == "" { 381 dictID = "english" 382 } 383 seed, err := modules.StringToSeed(req.FormValue("seed"), dictID) 384 if err != nil { 385 WriteError(w, Error{"error when calling /wallet/seed: " + err.Error()}, http.StatusBadRequest) 386 return 387 } 388 389 potentialKeys := encryptionKeys(req.FormValue("encryptionpassword")) 390 for _, key := range potentialKeys { 391 err := api.wallet.LoadSeed(key, seed) 392 if err == nil { 393 WriteSuccess(w) 394 return 395 } 396 if err != modules.ErrBadEncryptionKey { 397 WriteError(w, Error{"error when calling /wallet/seed: " + err.Error()}, http.StatusBadRequest) 398 return 399 } 400 } 401 WriteError(w, Error{"error when calling /wallet/seed: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 402 } 403 404 // walletSiagkeyHandler handles API calls to /wallet/siagkey. 405 func (api *API) walletSiagkeyHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 406 // Fetch the list of keyfiles from the post body. 407 keyfiles := strings.Split(req.FormValue("keyfiles"), ",") 408 potentialKeys := encryptionKeys(req.FormValue("encryptionpassword")) 409 410 for _, keypath := range keyfiles { 411 // Check that all key paths are absolute paths. 412 if !filepath.IsAbs(keypath) { 413 WriteError(w, Error{"error when calling /wallet/siagkey: keyfiles contains a non-absolute path"}, http.StatusBadRequest) 414 return 415 } 416 } 417 418 for _, key := range potentialKeys { 419 err := api.wallet.LoadSiagKeys(key, keyfiles) 420 if err == nil { 421 WriteSuccess(w) 422 return 423 } 424 if err != modules.ErrBadEncryptionKey { 425 WriteError(w, Error{"error when calling /wallet/siagkey: " + err.Error()}, http.StatusBadRequest) 426 return 427 } 428 } 429 WriteError(w, Error{"error when calling /wallet/siagkey: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 430 } 431 432 // walletLockHanlder handles API calls to /wallet/lock. 433 func (api *API) walletLockHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 434 err := api.wallet.Lock() 435 if err != nil { 436 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 437 return 438 } 439 WriteSuccess(w) 440 } 441 442 // walletSeedsHandler handles API calls to /wallet/seeds. 443 func (api *API) walletSeedsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 444 dictionary := mnemonics.DictionaryID(req.FormValue("dictionary")) 445 if dictionary == "" { 446 dictionary = mnemonics.English 447 } 448 449 // Get the primary seed information. 450 primarySeed, addrsRemaining, err := api.wallet.PrimarySeed() 451 if err != nil { 452 WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest) 453 return 454 } 455 primarySeedStr, err := modules.SeedToString(primarySeed, dictionary) 456 if err != nil { 457 WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest) 458 return 459 } 460 461 // Get the list of seeds known to the wallet. 462 allSeeds, err := api.wallet.AllSeeds() 463 if err != nil { 464 WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest) 465 return 466 } 467 var allSeedsStrs []string 468 for _, seed := range allSeeds { 469 str, err := modules.SeedToString(seed, dictionary) 470 if err != nil { 471 WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest) 472 return 473 } 474 allSeedsStrs = append(allSeedsStrs, str) 475 } 476 WriteJSON(w, WalletSeedsGET{ 477 PrimarySeed: primarySeedStr, 478 AddressesRemaining: int(addrsRemaining), 479 AllSeeds: allSeedsStrs, 480 }) 481 } 482 483 // walletSiacoinsHandler handles API calls to /wallet/siacoins. 484 func (api *API) walletSiacoinsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 485 var txns []types.Transaction 486 if req.FormValue("outputs") != "" { 487 // multiple amounts + destinations 488 if req.FormValue("amount") != "" || req.FormValue("destination") != "" { 489 WriteError(w, Error{"cannot supply both 'outputs' and single amount+destination pair"}, http.StatusInternalServerError) 490 return 491 } 492 493 var outputs []types.SiacoinOutput 494 err := json.Unmarshal([]byte(req.FormValue("outputs")), &outputs) 495 if err != nil { 496 WriteError(w, Error{"could not decode outputs: " + err.Error()}, http.StatusInternalServerError) 497 return 498 } 499 txns, err = api.wallet.SendSiacoinsMulti(outputs) 500 if err != nil { 501 WriteError(w, Error{"error when calling /wallet/siacoins: " + err.Error()}, http.StatusInternalServerError) 502 return 503 } 504 } else { 505 // single amount + destination 506 amount, ok := scanAmount(req.FormValue("amount")) 507 if !ok { 508 WriteError(w, Error{"could not read amount from POST call to /wallet/siacoins"}, http.StatusBadRequest) 509 return 510 } 511 dest, err := scanAddress(req.FormValue("destination")) 512 if err != nil { 513 WriteError(w, Error{"could not read address from POST call to /wallet/siacoins"}, http.StatusBadRequest) 514 return 515 } 516 517 txns, err = api.wallet.SendSiacoins(amount, dest) 518 if err != nil { 519 WriteError(w, Error{"error when calling /wallet/siacoins: " + err.Error()}, http.StatusInternalServerError) 520 return 521 } 522 523 } 524 525 var txids []types.TransactionID 526 for _, txn := range txns { 527 txids = append(txids, txn.ID()) 528 } 529 WriteJSON(w, WalletSiacoinsPOST{ 530 TransactionIDs: txids, 531 }) 532 } 533 534 // walletSiafundsHandler handles API calls to /wallet/siafunds. 535 func (api *API) walletSiafundsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 536 amount, ok := scanAmount(req.FormValue("amount")) 537 if !ok { 538 WriteError(w, Error{"could not read 'amount' from POST call to /wallet/siafunds"}, http.StatusBadRequest) 539 return 540 } 541 dest, err := scanAddress(req.FormValue("destination")) 542 if err != nil { 543 WriteError(w, Error{"error when calling /wallet/siafunds: " + err.Error()}, http.StatusBadRequest) 544 return 545 } 546 547 txns, err := api.wallet.SendSiafunds(amount, dest) 548 if err != nil { 549 WriteError(w, Error{"error when calling /wallet/siafunds: " + err.Error()}, http.StatusInternalServerError) 550 return 551 } 552 var txids []types.TransactionID 553 for _, txn := range txns { 554 txids = append(txids, txn.ID()) 555 } 556 WriteJSON(w, WalletSiafundsPOST{ 557 TransactionIDs: txids, 558 }) 559 } 560 561 // walletSweepSeedHandler handles API calls to /wallet/sweep/seed. 562 func (api *API) walletSweepSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 563 // Get the seed using the ditionary + phrase 564 dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) 565 if dictID == "" { 566 dictID = "english" 567 } 568 seed, err := modules.StringToSeed(req.FormValue("seed"), dictID) 569 if err != nil { 570 WriteError(w, Error{"error when calling /wallet/sweep/seed: " + err.Error()}, http.StatusBadRequest) 571 return 572 } 573 574 coins, funds, err := api.wallet.SweepSeed(seed) 575 if err != nil { 576 WriteError(w, Error{"error when calling /wallet/sweep/seed: " + err.Error()}, http.StatusBadRequest) 577 return 578 } 579 WriteJSON(w, WalletSweepPOST{ 580 Coins: coins, 581 Funds: funds, 582 }) 583 } 584 585 // walletTransactionHandler handles API calls to /wallet/transaction/:id. 586 func (api *API) walletTransactionHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 587 // Parse the id from the url. 588 var id types.TransactionID 589 jsonID := "\"" + ps.ByName("id") + "\"" 590 err := id.UnmarshalJSON([]byte(jsonID)) 591 if err != nil { 592 WriteError(w, Error{"error when calling /wallet/transaction/id:" + err.Error()}, http.StatusBadRequest) 593 return 594 } 595 596 txn, ok, err := api.wallet.Transaction(id) 597 if err != nil { 598 WriteError(w, Error{"error when calling /wallet/transaction/id:" + err.Error()}, http.StatusBadRequest) 599 return 600 } 601 if !ok { 602 WriteError(w, Error{"error when calling /wallet/transaction/:id : transaction not found"}, http.StatusBadRequest) 603 return 604 } 605 WriteJSON(w, WalletTransactionGETid{ 606 Transaction: txn, 607 }) 608 } 609 610 // walletTransactionsHandler handles API calls to /wallet/transactions. 611 func (api *API) walletTransactionsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 612 startheightStr, endheightStr, depthStr := req.FormValue("startheight"), req.FormValue("endheight"), req.FormValue("depth") 613 var start, end, depth uint64 614 var err error 615 if depthStr == "" { 616 if startheightStr == "" || endheightStr == "" { 617 WriteError(w, Error{"startheight and endheight must be provided to a /wallet/transactions call if depth is unspecified."}, http.StatusBadRequest) 618 return 619 } 620 // Get the start and end blocks. 621 start, err = strconv.ParseUint(startheightStr, 10, 64) 622 if err != nil { 623 WriteError(w, Error{"parsing integer value for parameter `startheight` failed: " + err.Error()}, http.StatusBadRequest) 624 return 625 } 626 // Check if endheightStr is set to -1. If it is, we use MaxUint64 as the 627 // end. Otherwise we parse the argument as an unsigned integer. 628 if endheightStr == "-1" { 629 end = math.MaxUint64 630 } else { 631 end, err = strconv.ParseUint(endheightStr, 10, 64) 632 } 633 if err != nil { 634 WriteError(w, Error{"parsing integer value for parameter `endheight` failed: " + err.Error()}, http.StatusBadRequest) 635 return 636 } 637 } else { 638 if startheightStr != "" || endheightStr != "" { 639 WriteError(w, Error{"startheight and endheight must not be provided to a /wallet/transactions call if depth is specified."}, http.StatusBadRequest) 640 return 641 } 642 // Get the start and end blocks by looking backwards from our current height. 643 depth, err = strconv.ParseUint(depthStr, 10, 64) 644 if err != nil { 645 WriteError(w, Error{"parsing integer value for parameter `depth` failed: " + err.Error()}, http.StatusBadRequest) 646 return 647 } 648 height, err := api.wallet.Height() 649 if err != nil { 650 WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest) 651 return 652 } 653 end = uint64(height) 654 start = end - depth - 1 655 if start < 0 { 656 start = 0 657 } 658 } 659 confirmedTxns, err := api.wallet.Transactions(types.BlockHeight(start), types.BlockHeight(end)) 660 if err != nil { 661 WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest) 662 return 663 } 664 unconfirmedTxns, err := api.wallet.UnconfirmedTransactions() 665 if err != nil { 666 WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest) 667 return 668 } 669 670 WriteJSON(w, WalletTransactionsGET{ 671 ConfirmedTransactions: confirmedTxns, 672 UnconfirmedTransactions: unconfirmedTxns, 673 }) 674 } 675 676 // walletTransactionsAddrHandler handles API calls to 677 // /wallet/transactions/:addr. 678 func (api *API) walletTransactionsAddrHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 679 // Parse the address being input. 680 jsonAddr := "\"" + ps.ByName("addr") + "\"" 681 var addr types.UnlockHash 682 err := addr.UnmarshalJSON([]byte(jsonAddr)) 683 if err != nil { 684 WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest) 685 return 686 } 687 688 confirmedATs, err := api.wallet.AddressTransactions(addr) 689 if err != nil { 690 WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest) 691 return 692 } 693 unconfirmedATs, err := api.wallet.AddressUnconfirmedTransactions(addr) 694 if err != nil { 695 WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest) 696 return 697 } 698 WriteJSON(w, WalletTransactionsGETaddr{ 699 ConfirmedTransactions: confirmedATs, 700 UnconfirmedTransactions: unconfirmedATs, 701 }) 702 } 703 704 // walletUnlockHandler handles API calls to /wallet/unlock. 705 func (api *API) walletUnlockHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 706 potentialKeys := encryptionKeys(req.FormValue("encryptionpassword")) 707 for _, key := range potentialKeys { 708 err := api.wallet.Unlock(key) 709 if err == nil { 710 WriteSuccess(w) 711 return 712 } 713 if err != modules.ErrBadEncryptionKey { 714 WriteError(w, Error{"error when calling /wallet/unlock: " + err.Error()}, http.StatusBadRequest) 715 return 716 } 717 } 718 WriteError(w, Error{"error when calling /wallet/unlock: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 719 } 720 721 // walletChangePasswordHandler handles API calls to /wallet/changepassword 722 func (api *API) walletChangePasswordHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 723 var newKey crypto.CipherKey 724 newPassword := req.FormValue("newpassword") 725 if newPassword == "" { 726 WriteError(w, Error{"a password must be provided to newpassword"}, http.StatusBadRequest) 727 return 728 } 729 newKey = crypto.NewWalletKey(crypto.HashObject(newPassword)) 730 731 originalKeys := encryptionKeys(req.FormValue("encryptionpassword")) 732 for _, key := range originalKeys { 733 err := api.wallet.ChangeKey(key, newKey) 734 if err == nil { 735 WriteSuccess(w) 736 return 737 } 738 if err != modules.ErrBadEncryptionKey { 739 WriteError(w, Error{"error when calling /wallet/changepassword: " + err.Error()}, http.StatusBadRequest) 740 return 741 } 742 } 743 WriteError(w, Error{"error when calling /wallet/changepassword: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest) 744 } 745 746 // walletVerifyAddressHandler handles API calls to /wallet/verify/address/:addr. 747 func (api *API) walletVerifyAddressHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 748 addrString := ps.ByName("addr") 749 750 err := new(types.UnlockHash).LoadString(addrString) 751 WriteJSON(w, WalletVerifyAddressGET{Valid: err == nil}) 752 } 753 754 // walletUnlockConditionsHandlerGET handles GET calls to /wallet/unlockconditions. 755 func (api *API) walletUnlockConditionsHandlerGET(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 756 var addr types.UnlockHash 757 err := addr.LoadString(ps.ByName("addr")) 758 if err != nil { 759 WriteError(w, Error{"error when calling /wallet/unlockconditions: " + err.Error()}, http.StatusBadRequest) 760 return 761 } 762 uc, err := api.wallet.UnlockConditions(addr) 763 if err != nil { 764 WriteError(w, Error{"error when calling /wallet/unlockconditions: " + err.Error()}, http.StatusBadRequest) 765 return 766 } 767 WriteJSON(w, WalletUnlockConditionsGET{ 768 UnlockConditions: uc, 769 }) 770 } 771 772 // walletUnlockConditionsHandlerPOST handles POST calls to /wallet/unlockconditions. 773 func (api *API) walletUnlockConditionsHandlerPOST(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 774 var params WalletUnlockConditionsPOSTParams 775 err := json.NewDecoder(req.Body).Decode(¶ms) 776 if err != nil { 777 WriteError(w, Error{"invalid parameters: " + err.Error()}, http.StatusBadRequest) 778 return 779 } 780 err = api.wallet.AddUnlockConditions(params.UnlockConditions) 781 if err != nil { 782 WriteError(w, Error{"error when calling /wallet/unlockconditions: " + err.Error()}, http.StatusBadRequest) 783 return 784 } 785 WriteSuccess(w) 786 } 787 788 // walletUnspentHandler handles API calls to /wallet/unspent. 789 func (api *API) walletUnspentHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 790 outputs, err := api.wallet.UnspentOutputs() 791 if err != nil { 792 WriteError(w, Error{"error when calling /wallet/unspent: " + err.Error()}, http.StatusInternalServerError) 793 return 794 } 795 WriteJSON(w, WalletUnspentGET{ 796 Outputs: outputs, 797 }) 798 } 799 800 // walletSignHandler handles API calls to /wallet/sign. 801 func (api *API) walletSignHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 802 var params WalletSignPOSTParams 803 err := json.NewDecoder(req.Body).Decode(¶ms) 804 if err != nil { 805 WriteError(w, Error{"invalid parameters: " + err.Error()}, http.StatusBadRequest) 806 return 807 } 808 err = api.wallet.SignTransaction(¶ms.Transaction, params.ToSign) 809 if err != nil { 810 WriteError(w, Error{"failed to sign transaction: " + err.Error()}, http.StatusBadRequest) 811 return 812 } 813 WriteJSON(w, WalletSignPOSTResp{ 814 Transaction: params.Transaction, 815 }) 816 } 817 818 // walletWatchHandlerGET handles GET calls to /wallet/watch. 819 func (api *API) walletWatchHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 820 addrs, err := api.wallet.WatchAddresses() 821 if err != nil { 822 WriteError(w, Error{"failed to get watch addresses: " + err.Error()}, http.StatusBadRequest) 823 return 824 } 825 WriteJSON(w, WalletWatchGET{ 826 Addresses: addrs, 827 }) 828 } 829 830 // walletWatchHandlerPOST handles POST calls to /wallet/watch. 831 func (api *API) walletWatchHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 832 var wwpp WalletWatchPOST 833 err := json.NewDecoder(req.Body).Decode(&wwpp) 834 if err != nil { 835 WriteError(w, Error{"invalid parameters: " + err.Error()}, http.StatusBadRequest) 836 return 837 } 838 if wwpp.Remove { 839 err = api.wallet.RemoveWatchAddresses(wwpp.Addresses, wwpp.Unused) 840 } else { 841 err = api.wallet.AddWatchAddresses(wwpp.Addresses, wwpp.Unused) 842 } 843 if err != nil { 844 WriteError(w, Error{"failed to update watch set: " + err.Error()}, http.StatusBadRequest) 845 return 846 } 847 WriteSuccess(w) 848 }