github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/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  	"SiaPrime/crypto"
    13  	"SiaPrime/modules"
    14  	"SiaPrime/types"
    15  
    16  	"github.com/julienschmidt/httprouter"
    17  	"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.TwofishKey) {
   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.TwofishKey(crypto.HashObject(seed)))
   163  	}
   164  	validKeys = append(validKeys, crypto.TwofishKey(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 != nil && 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  // walletAddressHandler handles API calls to /wallet/addresses.
   258  func (api *API) walletAddressesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   259  	addresses, err := api.wallet.AllAddresses()
   260  	if err != nil {
   261  		WriteError(w, Error{fmt.Sprintf("Error when calling /wallet/addresses: %v", err)}, http.StatusBadRequest)
   262  		return
   263  	}
   264  	WriteJSON(w, WalletAddressesGET{
   265  		Addresses: addresses,
   266  	})
   267  }
   268  
   269  // walletBackupHandler handles API calls to /wallet/backup.
   270  func (api *API) walletBackupHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   271  	destination := req.FormValue("destination")
   272  	// Check that the destination is absolute.
   273  	if !filepath.IsAbs(destination) {
   274  		WriteError(w, Error{"error when calling /wallet/backup: destination must be an absolute path"}, http.StatusBadRequest)
   275  		return
   276  	}
   277  	err := api.wallet.CreateBackup(destination)
   278  	if err != nil {
   279  		WriteError(w, Error{"error when calling /wallet/backup: " + err.Error()}, http.StatusBadRequest)
   280  		return
   281  	}
   282  	WriteSuccess(w)
   283  }
   284  
   285  // walletInitHandler handles API calls to /wallet/init.
   286  func (api *API) walletInitHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   287  	var encryptionKey crypto.TwofishKey
   288  	if req.FormValue("encryptionpassword") != "" {
   289  		encryptionKey = crypto.TwofishKey(crypto.HashObject(req.FormValue("encryptionpassword")))
   290  	}
   291  
   292  	if req.FormValue("force") == "true" {
   293  		err := api.wallet.Reset()
   294  		if err != nil {
   295  			WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest)
   296  			return
   297  		}
   298  	}
   299  	seed, err := api.wallet.Encrypt(encryptionKey)
   300  	if err != nil {
   301  		WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest)
   302  		return
   303  	}
   304  
   305  	dictID := mnemonics.DictionaryID(req.FormValue("dictionary"))
   306  	if dictID == "" {
   307  		dictID = "english"
   308  	}
   309  	seedStr, err := modules.SeedToString(seed, dictID)
   310  	if err != nil {
   311  		WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest)
   312  		return
   313  	}
   314  	WriteJSON(w, WalletInitPOST{
   315  		PrimarySeed: seedStr,
   316  	})
   317  }
   318  
   319  // walletInitSeedHandler handles API calls to /wallet/init/seed.
   320  func (api *API) walletInitSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   321  	var encryptionKey crypto.TwofishKey
   322  	if req.FormValue("encryptionpassword") != "" {
   323  		encryptionKey = crypto.TwofishKey(crypto.HashObject(req.FormValue("encryptionpassword")))
   324  	}
   325  	dictID := mnemonics.DictionaryID(req.FormValue("dictionary"))
   326  	if dictID == "" {
   327  		dictID = "english"
   328  	}
   329  	seed, err := modules.StringToSeed(req.FormValue("seed"), dictID)
   330  	if err != nil {
   331  		WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest)
   332  		return
   333  	}
   334  
   335  	if req.FormValue("force") == "true" {
   336  		err = api.wallet.Reset()
   337  		if err != nil {
   338  			WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest)
   339  			return
   340  		}
   341  	}
   342  
   343  	err = api.wallet.InitFromSeed(encryptionKey, seed)
   344  	if err != nil {
   345  		WriteError(w, Error{"error when calling /wallet/init/seed: " + err.Error()}, http.StatusBadRequest)
   346  		return
   347  	}
   348  	WriteSuccess(w)
   349  }
   350  
   351  // walletSeedHandler handles API calls to /wallet/seed.
   352  func (api *API) walletSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   353  	// Get the seed using the ditionary + phrase
   354  	dictID := mnemonics.DictionaryID(req.FormValue("dictionary"))
   355  	if dictID == "" {
   356  		dictID = "english"
   357  	}
   358  	seed, err := modules.StringToSeed(req.FormValue("seed"), dictID)
   359  	if err != nil {
   360  		WriteError(w, Error{"error when calling /wallet/seed: " + err.Error()}, http.StatusBadRequest)
   361  		return
   362  	}
   363  
   364  	potentialKeys := encryptionKeys(req.FormValue("encryptionpassword"))
   365  	for _, key := range potentialKeys {
   366  		err := api.wallet.LoadSeed(key, seed)
   367  		if err == nil {
   368  			WriteSuccess(w)
   369  			return
   370  		}
   371  		if err != nil && err != modules.ErrBadEncryptionKey {
   372  			WriteError(w, Error{"error when calling /wallet/seed: " + err.Error()}, http.StatusBadRequest)
   373  			return
   374  		}
   375  	}
   376  	WriteError(w, Error{"error when calling /wallet/seed: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest)
   377  }
   378  
   379  // walletSiagkeyHandler handles API calls to /wallet/siagkey.
   380  func (api *API) walletSiagkeyHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   381  	// Fetch the list of keyfiles from the post body.
   382  	keyfiles := strings.Split(req.FormValue("keyfiles"), ",")
   383  	potentialKeys := encryptionKeys(req.FormValue("encryptionpassword"))
   384  
   385  	for _, keypath := range keyfiles {
   386  		// Check that all key paths are absolute paths.
   387  		if !filepath.IsAbs(keypath) {
   388  			WriteError(w, Error{"error when calling /wallet/siagkey: keyfiles contains a non-absolute path"}, http.StatusBadRequest)
   389  			return
   390  		}
   391  	}
   392  
   393  	for _, key := range potentialKeys {
   394  		err := api.wallet.LoadSiagKeys(key, keyfiles)
   395  		if err == nil {
   396  			WriteSuccess(w)
   397  			return
   398  		}
   399  		if err != nil && err != modules.ErrBadEncryptionKey {
   400  			WriteError(w, Error{"error when calling /wallet/siagkey: " + err.Error()}, http.StatusBadRequest)
   401  			return
   402  		}
   403  	}
   404  	WriteError(w, Error{"error when calling /wallet/siagkey: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest)
   405  }
   406  
   407  // walletLockHanlder handles API calls to /wallet/lock.
   408  func (api *API) walletLockHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   409  	err := api.wallet.Lock()
   410  	if err != nil {
   411  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   412  		return
   413  	}
   414  	WriteSuccess(w)
   415  }
   416  
   417  // walletSeedsHandler handles API calls to /wallet/seeds.
   418  func (api *API) walletSeedsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   419  	dictionary := mnemonics.DictionaryID(req.FormValue("dictionary"))
   420  	if dictionary == "" {
   421  		dictionary = mnemonics.English
   422  	}
   423  
   424  	// Get the primary seed information.
   425  	primarySeed, addrsRemaining, err := api.wallet.PrimarySeed()
   426  	if err != nil {
   427  		WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest)
   428  		return
   429  	}
   430  	primarySeedStr, err := modules.SeedToString(primarySeed, dictionary)
   431  	if err != nil {
   432  		WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest)
   433  		return
   434  	}
   435  
   436  	// Get the list of seeds known to the wallet.
   437  	allSeeds, err := api.wallet.AllSeeds()
   438  	if err != nil {
   439  		WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest)
   440  		return
   441  	}
   442  	var allSeedsStrs []string
   443  	for _, seed := range allSeeds {
   444  		str, err := modules.SeedToString(seed, dictionary)
   445  		if err != nil {
   446  			WriteError(w, Error{"error when calling /wallet/seeds: " + err.Error()}, http.StatusBadRequest)
   447  			return
   448  		}
   449  		allSeedsStrs = append(allSeedsStrs, str)
   450  	}
   451  	WriteJSON(w, WalletSeedsGET{
   452  		PrimarySeed:        primarySeedStr,
   453  		AddressesRemaining: int(addrsRemaining),
   454  		AllSeeds:           allSeedsStrs,
   455  	})
   456  }
   457  
   458  // walletSiacoinsHandler handles API calls to /wallet/siacoins.
   459  func (api *API) walletSiacoinsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   460  	var txns []types.Transaction
   461  	if req.FormValue("outputs") != "" {
   462  		// multiple amounts + destinations
   463  		if req.FormValue("amount") != "" || req.FormValue("destination") != "" {
   464  			WriteError(w, Error{"cannot supply both 'outputs' and single amount+destination pair"}, http.StatusInternalServerError)
   465  			return
   466  		}
   467  
   468  		var outputs []types.SiacoinOutput
   469  		err := json.Unmarshal([]byte(req.FormValue("outputs")), &outputs)
   470  		if err != nil {
   471  			WriteError(w, Error{"could not decode outputs: " + err.Error()}, http.StatusInternalServerError)
   472  			return
   473  		}
   474  		txns, err = api.wallet.SendSiacoinsMulti(outputs)
   475  		if err != nil {
   476  			WriteError(w, Error{"error when calling /wallet/siacoins: " + err.Error()}, http.StatusInternalServerError)
   477  			return
   478  		}
   479  	} else {
   480  		// single amount + destination
   481  		amount, ok := scanAmount(req.FormValue("amount"))
   482  		if !ok {
   483  			WriteError(w, Error{"could not read amount from POST call to /wallet/siacoins"}, http.StatusBadRequest)
   484  			return
   485  		}
   486  		dest, err := scanAddress(req.FormValue("destination"))
   487  		if err != nil {
   488  			WriteError(w, Error{"could not read address from POST call to /wallet/siacoins"}, http.StatusBadRequest)
   489  			return
   490  		}
   491  
   492  		txns, err = api.wallet.SendSiacoins(amount, dest)
   493  		if err != nil {
   494  			WriteError(w, Error{"error when calling /wallet/siacoins: " + err.Error()}, http.StatusInternalServerError)
   495  			return
   496  		}
   497  
   498  	}
   499  
   500  	var txids []types.TransactionID
   501  	for _, txn := range txns {
   502  		txids = append(txids, txn.ID())
   503  	}
   504  	WriteJSON(w, WalletSiacoinsPOST{
   505  		TransactionIDs: txids,
   506  	})
   507  }
   508  
   509  // walletSiafundsHandler handles API calls to /wallet/siafunds.
   510  func (api *API) walletSiafundsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   511  	amount, ok := scanAmount(req.FormValue("amount"))
   512  	if !ok {
   513  		WriteError(w, Error{"could not read 'amount' from POST call to /wallet/siafunds"}, http.StatusBadRequest)
   514  		return
   515  	}
   516  	dest, err := scanAddress(req.FormValue("destination"))
   517  	if err != nil {
   518  		WriteError(w, Error{"error when calling /wallet/siafunds: " + err.Error()}, http.StatusBadRequest)
   519  		return
   520  	}
   521  
   522  	txns, err := api.wallet.SendSiafunds(amount, dest)
   523  	if err != nil {
   524  		WriteError(w, Error{"error when calling /wallet/siafunds: " + err.Error()}, http.StatusInternalServerError)
   525  		return
   526  	}
   527  	var txids []types.TransactionID
   528  	for _, txn := range txns {
   529  		txids = append(txids, txn.ID())
   530  	}
   531  	WriteJSON(w, WalletSiafundsPOST{
   532  		TransactionIDs: txids,
   533  	})
   534  }
   535  
   536  // walletSweepSeedHandler handles API calls to /wallet/sweep/seed.
   537  func (api *API) walletSweepSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   538  	// Get the seed using the ditionary + phrase
   539  	dictID := mnemonics.DictionaryID(req.FormValue("dictionary"))
   540  	if dictID == "" {
   541  		dictID = "english"
   542  	}
   543  	seed, err := modules.StringToSeed(req.FormValue("seed"), dictID)
   544  	if err != nil {
   545  		WriteError(w, Error{"error when calling /wallet/sweep/seed: " + err.Error()}, http.StatusBadRequest)
   546  		return
   547  	}
   548  
   549  	coins, funds, err := api.wallet.SweepSeed(seed)
   550  	if err != nil {
   551  		WriteError(w, Error{"error when calling /wallet/sweep/seed: " + err.Error()}, http.StatusBadRequest)
   552  		return
   553  	}
   554  	WriteJSON(w, WalletSweepPOST{
   555  		Coins: coins,
   556  		Funds: funds,
   557  	})
   558  }
   559  
   560  // walletTransactionHandler handles API calls to /wallet/transaction/:id.
   561  func (api *API) walletTransactionHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   562  	// Parse the id from the url.
   563  	var id types.TransactionID
   564  	jsonID := "\"" + ps.ByName("id") + "\""
   565  	err := id.UnmarshalJSON([]byte(jsonID))
   566  	if err != nil {
   567  		WriteError(w, Error{"error when calling /wallet/transaction/id:" + err.Error()}, http.StatusBadRequest)
   568  		return
   569  	}
   570  
   571  	txn, ok, err := api.wallet.Transaction(id)
   572  	if err != nil {
   573  		WriteError(w, Error{"error when calling /wallet/transaction/id:" + err.Error()}, http.StatusBadRequest)
   574  		return
   575  	}
   576  	if !ok {
   577  		WriteError(w, Error{"error when calling /wallet/transaction/:id  :  transaction not found"}, http.StatusBadRequest)
   578  		return
   579  	}
   580  	WriteJSON(w, WalletTransactionGETid{
   581  		Transaction: txn,
   582  	})
   583  }
   584  
   585  // walletTransactionsHandler handles API calls to /wallet/transactions.
   586  func (api *API) walletTransactionsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   587  	startheightStr, endheightStr, depthStr := req.FormValue("startheight"), req.FormValue("endheight"), req.FormValue("depth")
   588  	var start, end, depth uint64
   589  	var err error
   590  	if depthStr == "" {
   591  		if startheightStr == "" || endheightStr == "" {
   592  			WriteError(w, Error{"startheight and endheight must be provided to a /wallet/transactions call if depth is unspecified."}, http.StatusBadRequest)
   593  			return
   594  		}
   595  		// Get the start and end blocks.
   596  		start, err = strconv.ParseUint(startheightStr, 10, 64)
   597  		if err != nil {
   598  			WriteError(w, Error{"parsing integer value for parameter `startheight` failed: " + err.Error()}, http.StatusBadRequest)
   599  			return
   600  		}
   601  		// Check if endheightStr is set to -1. If it is, we use MaxUint64 as the
   602  		// end. Otherwise we parse the argument as an unsigned integer.
   603  		if endheightStr == "-1" {
   604  			end = math.MaxUint64
   605  		} else {
   606  			end, err = strconv.ParseUint(endheightStr, 10, 64)
   607  		}
   608  		if err != nil {
   609  			WriteError(w, Error{"parsing integer value for parameter `endheight` failed: " + err.Error()}, http.StatusBadRequest)
   610  			return
   611  		}
   612  	} else {
   613  		if startheightStr != "" || endheightStr != "" {
   614  			WriteError(w, Error{"startheight and endheight must not be provided to a /wallet/transactions call if depth is specified."}, http.StatusBadRequest)
   615  			return
   616  		}
   617  		// Get the start and end blocks by looking backwards from our current height.
   618  		depth, err = strconv.ParseUint(depthStr, 10, 64)
   619  		if err != nil {
   620  			WriteError(w, Error{"parsing integer value for parameter `depth` failed: " + err.Error()}, http.StatusBadRequest)
   621  			return
   622  		}
   623  		height, err := api.wallet.Height()
   624  		if err != nil {
   625  			WriteError(w, Error{fmt.Sprintf("Error when calling /wallet: %v", err)}, http.StatusBadRequest)
   626  			return
   627  		}
   628  		end = uint64(height)
   629  		start = end - depth - 1
   630  		if start < 0 {
   631  			start = 0
   632  		}
   633  	}
   634  	confirmedTxns, err := api.wallet.Transactions(types.BlockHeight(start), types.BlockHeight(end))
   635  	if err != nil {
   636  		WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest)
   637  		return
   638  	}
   639  	unconfirmedTxns, err := api.wallet.UnconfirmedTransactions()
   640  	if err != nil {
   641  		WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest)
   642  		return
   643  	}
   644  
   645  	WriteJSON(w, WalletTransactionsGET{
   646  		ConfirmedTransactions:   confirmedTxns,
   647  		UnconfirmedTransactions: unconfirmedTxns,
   648  	})
   649  }
   650  
   651  // walletTransactionsAddrHandler handles API calls to
   652  // /wallet/transactions/:addr.
   653  func (api *API) walletTransactionsAddrHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   654  	// Parse the address being input.
   655  	jsonAddr := "\"" + ps.ByName("addr") + "\""
   656  	var addr types.UnlockHash
   657  	err := addr.UnmarshalJSON([]byte(jsonAddr))
   658  	if err != nil {
   659  		WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest)
   660  		return
   661  	}
   662  
   663  	confirmedATs, err := api.wallet.AddressTransactions(addr)
   664  	if err != nil {
   665  		WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest)
   666  		return
   667  	}
   668  	unconfirmedATs, err := api.wallet.AddressUnconfirmedTransactions(addr)
   669  	if err != nil {
   670  		WriteError(w, Error{"error when calling /wallet/transactions: " + err.Error()}, http.StatusBadRequest)
   671  		return
   672  	}
   673  	WriteJSON(w, WalletTransactionsGETaddr{
   674  		ConfirmedTransactions:   confirmedATs,
   675  		UnconfirmedTransactions: unconfirmedATs,
   676  	})
   677  }
   678  
   679  // walletUnlockHandler handles API calls to /wallet/unlock.
   680  func (api *API) walletUnlockHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   681  	potentialKeys := encryptionKeys(req.FormValue("encryptionpassword"))
   682  	for _, key := range potentialKeys {
   683  		err := api.wallet.Unlock(key)
   684  		if err == nil {
   685  			WriteSuccess(w)
   686  			return
   687  		}
   688  		if err != nil && err != modules.ErrBadEncryptionKey {
   689  			WriteError(w, Error{"error when calling /wallet/unlock: " + err.Error()}, http.StatusBadRequest)
   690  			return
   691  		}
   692  	}
   693  	WriteError(w, Error{"error when calling /wallet/unlock: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest)
   694  }
   695  
   696  // walletChangePasswordHandler handles API calls to /wallet/changepassword
   697  func (api *API) walletChangePasswordHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   698  	var newKey crypto.TwofishKey
   699  	newPassword := req.FormValue("newpassword")
   700  	if newPassword == "" {
   701  		WriteError(w, Error{"a password must be provided to newpassword"}, http.StatusBadRequest)
   702  		return
   703  	}
   704  	newKey = crypto.TwofishKey(crypto.HashObject(newPassword))
   705  
   706  	originalKeys := encryptionKeys(req.FormValue("encryptionpassword"))
   707  	for _, key := range originalKeys {
   708  		err := api.wallet.ChangeKey(key, newKey)
   709  		if err == nil {
   710  			WriteSuccess(w)
   711  			return
   712  		}
   713  		if err != nil && err != modules.ErrBadEncryptionKey {
   714  			WriteError(w, Error{"error when calling /wallet/changepassword: " + err.Error()}, http.StatusBadRequest)
   715  			return
   716  		}
   717  	}
   718  	WriteError(w, Error{"error when calling /wallet/changepassword: " + modules.ErrBadEncryptionKey.Error()}, http.StatusBadRequest)
   719  }
   720  
   721  // walletVerifyAddressHandler handles API calls to /wallet/verify/address/:addr.
   722  func (api *API) walletVerifyAddressHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   723  	addrString := ps.ByName("addr")
   724  
   725  	err := new(types.UnlockHash).LoadString(addrString)
   726  	WriteJSON(w, WalletVerifyAddressGET{Valid: err == nil})
   727  }
   728  
   729  // walletUnlockConditionsHandlerGET handles GET calls to /wallet/unlockconditions.
   730  func (api *API) walletUnlockConditionsHandlerGET(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   731  	var addr types.UnlockHash
   732  	err := addr.LoadString(ps.ByName("addr"))
   733  	if err != nil {
   734  		WriteError(w, Error{"error when calling /wallet/unlockconditions: " + err.Error()}, http.StatusBadRequest)
   735  		return
   736  	}
   737  	uc, err := api.wallet.UnlockConditions(addr)
   738  	if err != nil {
   739  		WriteError(w, Error{"error when calling /wallet/unlockconditions: " + err.Error()}, http.StatusBadRequest)
   740  		return
   741  	}
   742  	WriteJSON(w, WalletUnlockConditionsGET{
   743  		UnlockConditions: uc,
   744  	})
   745  }
   746  
   747  // walletUnlockConditionsHandlerPOST handles POST calls to /wallet/unlockconditions.
   748  func (api *API) walletUnlockConditionsHandlerPOST(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   749  	var params WalletUnlockConditionsPOSTParams
   750  	err := json.NewDecoder(req.Body).Decode(&params)
   751  	if err != nil {
   752  		WriteError(w, Error{"invalid parameters: " + err.Error()}, http.StatusBadRequest)
   753  		return
   754  	}
   755  	err = api.wallet.AddUnlockConditions(params.UnlockConditions)
   756  	if err != nil {
   757  		WriteError(w, Error{"error when calling /wallet/unlockconditions: " + err.Error()}, http.StatusBadRequest)
   758  		return
   759  	}
   760  	WriteSuccess(w)
   761  }
   762  
   763  // walletUnspentHandler handles API calls to /wallet/unspent.
   764  func (api *API) walletUnspentHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   765  	outputs, err := api.wallet.UnspentOutputs()
   766  	if err != nil {
   767  		WriteError(w, Error{"error when calling /wallet/unspent: " + err.Error()}, http.StatusInternalServerError)
   768  		return
   769  	}
   770  	WriteJSON(w, WalletUnspentGET{
   771  		Outputs: outputs,
   772  	})
   773  }
   774  
   775  // walletSignHandler handles API calls to /wallet/sign.
   776  func (api *API) walletSignHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   777  	var params WalletSignPOSTParams
   778  	err := json.NewDecoder(req.Body).Decode(&params)
   779  	if err != nil {
   780  		WriteError(w, Error{"invalid parameters: " + err.Error()}, http.StatusBadRequest)
   781  		return
   782  	}
   783  	err = api.wallet.SignTransaction(&params.Transaction, params.ToSign)
   784  	if err != nil {
   785  		WriteError(w, Error{"failed to sign transaction: " + err.Error()}, http.StatusBadRequest)
   786  		return
   787  	}
   788  	WriteJSON(w, WalletSignPOSTResp{
   789  		Transaction: params.Transaction,
   790  	})
   791  }
   792  
   793  // walletWatchHandlerGET handles GET calls to /wallet/watch.
   794  func (api *API) walletWatchHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   795  	addrs, err := api.wallet.WatchAddresses()
   796  	if err != nil {
   797  		WriteError(w, Error{"failed to get watch addresses: " + err.Error()}, http.StatusBadRequest)
   798  		return
   799  	}
   800  	WriteJSON(w, WalletWatchGET{
   801  		Addresses: addrs,
   802  	})
   803  }
   804  
   805  // walletWatchHandlerPOST handles POST calls to /wallet/watch.
   806  func (api *API) walletWatchHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   807  	var wwpp WalletWatchPOST
   808  	err := json.NewDecoder(req.Body).Decode(&wwpp)
   809  	if err != nil {
   810  		WriteError(w, Error{"invalid parameters: " + err.Error()}, http.StatusBadRequest)
   811  		return
   812  	}
   813  	if wwpp.Remove {
   814  		err = api.wallet.RemoveWatchAddresses(wwpp.Addresses, wwpp.Unused)
   815  	} else {
   816  		err = api.wallet.AddWatchAddresses(wwpp.Addresses, wwpp.Unused)
   817  	}
   818  	if err != nil {
   819  		WriteError(w, Error{"failed to update watch set: " + err.Error()}, http.StatusBadRequest)
   820  		return
   821  	}
   822  	WriteSuccess(w)
   823  }