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(&params)
   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(&params)
   804  	if err != nil {
   805  		WriteError(w, Error{"invalid parameters: " + err.Error()}, http.StatusBadRequest)
   806  		return
   807  	}
   808  	err = api.wallet.SignTransaction(&params.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  }