github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/api/wallet.go (about)

     1  package api
     2  
     3  import (
     4  	"net/http"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/NebulousLabs/Sia/crypto"
     9  	"github.com/NebulousLabs/Sia/modules"
    10  	"github.com/NebulousLabs/Sia/types"
    11  
    12  	"github.com/NebulousLabs/entropy-mnemonics"
    13  	"github.com/julienschmidt/httprouter"
    14  )
    15  
    16  type (
    17  	// WalletGET contains general information about the wallet.
    18  	WalletGET struct {
    19  		Encrypted bool `json:"encrypted"`
    20  		Unlocked  bool `json:"unlocked"`
    21  
    22  		ConfirmedSiacoinBalance     types.Currency `json:"confirmedsiacoinbalance"`
    23  		UnconfirmedOutgoingSiacoins types.Currency `json:"unconfirmedoutgoingsiacoins"`
    24  		UnconfirmedIncomingSiacoins types.Currency `json:"unconfirmedincomingsiacoins"`
    25  
    26  		SiafundBalance      types.Currency `json:"siafundbalance"`
    27  		SiacoinClaimBalance types.Currency `json:"siacoinclaimbalance"`
    28  	}
    29  
    30  	// WalletAddressGET contains an address returned by a GET call to
    31  	// /wallet/address.
    32  	WalletAddressGET struct {
    33  		Address types.UnlockHash `json:"address"`
    34  	}
    35  
    36  	// WalletAddressesGET contains the list of wallet addresses returned by a
    37  	// GET call to /wallet/addresses.
    38  	WalletAddressesGET struct {
    39  		Addresses []types.UnlockHash `json:"addresses"`
    40  	}
    41  
    42  	// WalletInitPOST contains the primary seed that gets generated during a
    43  	// POST call to /wallet/init.
    44  	WalletInitPOST struct {
    45  		PrimarySeed string `json:"primaryseed"`
    46  	}
    47  
    48  	// WalletEncryptPOST contains the primary seed that gets generated during a
    49  	// POST call to /wallet/encrypt.
    50  	//
    51  	// COMPATv0.4.0
    52  	WalletEncryptPOST struct {
    53  		PrimarySeed string `json:"primaryseed"`
    54  	}
    55  
    56  	// WalletSiacoinsPOST contains the transaction sent in the POST call to
    57  	// /wallet/siafunds.
    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  	// WalletSeedsGET contains the seeds used by the wallet.
    69  	WalletSeedsGET struct {
    70  		PrimarySeed        string   `json:"primaryseed"`
    71  		AddressesRemaining int      `json:"addressesremaining"`
    72  		AllSeeds           []string `json:"allseeds"`
    73  	}
    74  
    75  	// WalletTransactionGETid contains the transaction returned by a call to
    76  	// /wallet/transaction/$(id)
    77  	WalletTransactionGETid struct {
    78  		Transaction modules.ProcessedTransaction `json:"transaction"`
    79  	}
    80  
    81  	// WalletTransactionsGET contains the specified set of confirmed and
    82  	// unconfirmed transactions.
    83  	WalletTransactionsGET struct {
    84  		ConfirmedTransactions   []modules.ProcessedTransaction `json:"confirmedtransactions"`
    85  		UnconfirmedTransactions []modules.ProcessedTransaction `json:"unconfirmedtransactions"`
    86  	}
    87  
    88  	// WalletTransactionsGETaddr contains the set of wallet transactions
    89  	// relevant to the input address provided in the call to
    90  	// /wallet/transaction/$(addr)
    91  	WalletTransactionsGETaddr struct {
    92  		ConfirmedTransactions   []modules.ProcessedTransaction `json:"confirmedtransactions"`
    93  		UnconfirmedTransactions []modules.ProcessedTransaction `json:"unconfirmedtransactions"`
    94  	}
    95  )
    96  
    97  // encryptionKeys enumerates the possible encryption keys that can be derived
    98  // from an input string.
    99  func encryptionKeys(seedStr string) (validKeys []crypto.TwofishKey) {
   100  	dicts := []mnemonics.DictionaryID{"english", "german", "japanese"}
   101  	for _, dict := range dicts {
   102  		seed, err := modules.StringToSeed(seedStr, dict)
   103  		if err != nil {
   104  			continue
   105  		}
   106  		validKeys = append(validKeys, crypto.TwofishKey(crypto.HashObject(seed)))
   107  	}
   108  	validKeys = append(validKeys, crypto.TwofishKey(crypto.HashObject(seedStr)))
   109  	return validKeys
   110  }
   111  
   112  // walletHander handles API calls to /wallet.
   113  func (srv *Server) walletHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   114  	siacoinBal, siafundBal, siaclaimBal := srv.wallet.ConfirmedBalance()
   115  	siacoinsOut, siacoinsIn := srv.wallet.UnconfirmedBalance()
   116  	writeJSON(w, WalletGET{
   117  		Encrypted: srv.wallet.Encrypted(),
   118  		Unlocked:  srv.wallet.Unlocked(),
   119  
   120  		ConfirmedSiacoinBalance:     siacoinBal,
   121  		UnconfirmedOutgoingSiacoins: siacoinsOut,
   122  		UnconfirmedIncomingSiacoins: siacoinsIn,
   123  
   124  		SiafundBalance:      siafundBal,
   125  		SiacoinClaimBalance: siaclaimBal,
   126  	})
   127  }
   128  
   129  // wallet033xHandler handles API calls to /wallet/033x.
   130  func (srv *Server) wallet033xHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   131  	source := req.FormValue("source")
   132  	potentialKeys := encryptionKeys(req.FormValue("encryptionpassword"))
   133  	for _, key := range potentialKeys {
   134  		err := srv.wallet.Load033xWallet(key, source)
   135  		if err == nil {
   136  			writeSuccess(w)
   137  			return
   138  		}
   139  		if err != nil && err != modules.ErrBadEncryptionKey {
   140  			writeError(w, "error when calling /wallet/033x: "+err.Error(), http.StatusBadRequest)
   141  			return
   142  		}
   143  	}
   144  	writeError(w, modules.ErrBadEncryptionKey.Error(), http.StatusBadRequest)
   145  }
   146  
   147  // walletAddressHandler handles API calls to /wallet/address.
   148  func (srv *Server) walletAddressHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   149  	unlockConditions, err := srv.wallet.NextAddress()
   150  	if err != nil {
   151  		writeError(w, "error after call to /wallet/addresses: "+err.Error(), http.StatusBadRequest)
   152  		return
   153  	}
   154  	writeJSON(w, WalletAddressGET{
   155  		Address: unlockConditions.UnlockHash(),
   156  	})
   157  }
   158  
   159  // walletAddressHandler handles API calls to /wallet/addresses.
   160  func (srv *Server) walletAddressesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   161  	writeJSON(w, WalletAddressesGET{
   162  		Addresses: srv.wallet.AllAddresses(),
   163  	})
   164  }
   165  
   166  // walletBackupHandler handles API calls to /wallet/backup.
   167  func (srv *Server) walletBackupHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   168  	err := srv.wallet.CreateBackup(req.FormValue("destination"))
   169  	if err != nil {
   170  		writeError(w, "error after call to /wallet/backup: "+err.Error(), http.StatusBadRequest)
   171  		return
   172  	}
   173  	writeSuccess(w)
   174  }
   175  
   176  // walletInitHandler handles API calls to /wallet/init.
   177  func (srv *Server) walletInitHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   178  	var encryptionKey crypto.TwofishKey
   179  	if req.FormValue("encryptionpassword") != "" {
   180  		encryptionKey = crypto.TwofishKey(crypto.HashObject(req.FormValue("encryptionpassword")))
   181  	}
   182  	seed, err := srv.wallet.Encrypt(encryptionKey)
   183  	if err != nil {
   184  		writeError(w, "error when calling /wallet/init: "+err.Error(), http.StatusBadRequest)
   185  		return
   186  	}
   187  
   188  	dictID := mnemonics.DictionaryID(req.FormValue("dictionary"))
   189  	if dictID == "" {
   190  		dictID = "english"
   191  	}
   192  	seedStr, err := modules.SeedToString(seed, dictID)
   193  	if err != nil {
   194  		writeError(w, "error when calling /wallet/init: "+err.Error(), http.StatusBadRequest)
   195  		return
   196  	}
   197  	writeJSON(w, WalletInitPOST{
   198  		PrimarySeed: seedStr,
   199  	})
   200  }
   201  
   202  // walletSeedHandler handles API calls to /wallet/seed.
   203  func (srv *Server) walletSeedHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   204  	// Get the seed using the ditionary + phrase
   205  	dictID := mnemonics.DictionaryID(req.FormValue("dictionary"))
   206  	seed, err := modules.StringToSeed(req.FormValue("seed"), dictID)
   207  	if err != nil {
   208  		writeError(w, "error when calling /wallet/seed: "+err.Error(), http.StatusBadRequest)
   209  		return
   210  	}
   211  
   212  	potentialKeys := encryptionKeys(req.FormValue("encryptionpassword"))
   213  	for _, key := range potentialKeys {
   214  		err := srv.wallet.LoadSeed(key, seed)
   215  		if err == nil {
   216  			writeSuccess(w)
   217  			return
   218  		}
   219  		if err != nil && err != modules.ErrBadEncryptionKey {
   220  			writeError(w, "error when calling /wallet/seed: "+err.Error(), http.StatusBadRequest)
   221  			return
   222  		}
   223  	}
   224  	writeError(w, "error when calling /wallet/seed: "+modules.ErrBadEncryptionKey.Error(), http.StatusBadRequest)
   225  }
   226  
   227  // walletSiagkeyHandler handles API calls to /wallet/siagkey.
   228  func (srv *Server) walletSiagkeyHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   229  	// Fetch the list of keyfiles from the post body.
   230  	keyfiles := strings.Split(req.FormValue("keyfiles"), ",")
   231  	potentialKeys := encryptionKeys(req.FormValue("encryptionpassword"))
   232  	for _, key := range potentialKeys {
   233  		err := srv.wallet.LoadSiagKeys(key, keyfiles)
   234  		if err == nil {
   235  			writeSuccess(w)
   236  			return
   237  		}
   238  		if err != nil && err != modules.ErrBadEncryptionKey {
   239  			writeError(w, "error when calling /wallet/siagkey: "+err.Error(), http.StatusBadRequest)
   240  			return
   241  		}
   242  	}
   243  	writeError(w, "error when calling /wallet/siagkey: "+modules.ErrBadEncryptionKey.Error(), http.StatusBadRequest)
   244  }
   245  
   246  // walletLockHanlder handles API calls to /wallet/lock.
   247  func (srv *Server) walletLockHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   248  	err := srv.wallet.Lock()
   249  	if err != nil {
   250  		writeError(w, err.Error(), http.StatusBadRequest)
   251  		return
   252  	}
   253  	writeSuccess(w)
   254  }
   255  
   256  // walletSeedHandler handles API calls to /wallet/seed.
   257  func (srv *Server) walletSeedsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   258  	dictionary := mnemonics.DictionaryID(req.FormValue("dictionary"))
   259  	if dictionary == "" {
   260  		dictionary = mnemonics.English
   261  	}
   262  
   263  	// Get the primary seed information.
   264  	primarySeed, progress, err := srv.wallet.PrimarySeed()
   265  	if err != nil {
   266  		writeError(w, "error after call to /wallet/seed: "+err.Error(), http.StatusBadRequest)
   267  		return
   268  	}
   269  	primarySeedStr, err := modules.SeedToString(primarySeed, dictionary)
   270  	if err != nil {
   271  		writeError(w, "error after call to /wallet/seed: "+err.Error(), http.StatusBadRequest)
   272  		return
   273  	}
   274  
   275  	// Get the list of seeds known to the wallet.
   276  	allSeeds, err := srv.wallet.AllSeeds()
   277  	if err != nil {
   278  		writeError(w, "error after call to /wallet/seed: "+err.Error(), http.StatusBadRequest)
   279  		return
   280  	}
   281  	var allSeedsStrs []string
   282  	for _, seed := range allSeeds {
   283  		str, err := modules.SeedToString(seed, dictionary)
   284  		if err != nil {
   285  			writeError(w, "error after call to /wallet/seed: "+err.Error(), http.StatusBadRequest)
   286  			return
   287  		}
   288  		allSeedsStrs = append(allSeedsStrs, str)
   289  	}
   290  	writeJSON(w, WalletSeedsGET{
   291  		PrimarySeed:        primarySeedStr,
   292  		AddressesRemaining: int(modules.PublicKeysPerSeed - progress),
   293  		AllSeeds:           allSeedsStrs,
   294  	})
   295  }
   296  
   297  // walletSiacoinsHandler handles API calls to /wallet/siacoins.
   298  func (srv *Server) walletSiacoinsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   299  	amount, ok := scanAmount(req.FormValue("amount"))
   300  	if !ok {
   301  		writeError(w, "could not read 'amount' from POST call to /wallet/siacoins", http.StatusBadRequest)
   302  		return
   303  	}
   304  	dest, err := scanAddress(req.FormValue("destination"))
   305  	if err != nil {
   306  		writeError(w, "error after call to /wallet/siacoins: "+err.Error(), http.StatusBadRequest)
   307  		return
   308  	}
   309  
   310  	txns, err := srv.wallet.SendSiacoins(amount, dest)
   311  	if err != nil {
   312  		writeError(w, "error after call to /wallet/siacoins: "+err.Error(), http.StatusInternalServerError)
   313  		return
   314  	}
   315  	var txids []types.TransactionID
   316  	for _, txn := range txns {
   317  		txids = append(txids, txn.ID())
   318  	}
   319  	writeJSON(w, WalletSiacoinsPOST{
   320  		TransactionIDs: txids,
   321  	})
   322  }
   323  
   324  // walletSiafundsHandler handles API calls to /wallet/siafunds.
   325  func (srv *Server) walletSiafundsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   326  	amount, ok := scanAmount(req.FormValue("amount"))
   327  	if !ok {
   328  		writeError(w, "could not read 'amount' from POST call to /wallet/siafunds", http.StatusBadRequest)
   329  		return
   330  	}
   331  	dest, err := scanAddress(req.FormValue("destination"))
   332  	if err != nil {
   333  		writeError(w, "error after call to /wallet/siafunds: "+err.Error(), http.StatusBadRequest)
   334  		return
   335  	}
   336  
   337  	txns, err := srv.wallet.SendSiafunds(amount, dest)
   338  	if err != nil {
   339  		writeError(w, "error after call to /wallet/siafunds: "+err.Error(), http.StatusInternalServerError)
   340  		return
   341  	}
   342  	var txids []types.TransactionID
   343  	for _, txn := range txns {
   344  		txids = append(txids, txn.ID())
   345  	}
   346  	writeJSON(w, WalletSiafundsPOST{
   347  		TransactionIDs: txids,
   348  	})
   349  }
   350  
   351  // walletTransactionHandler handles API calls to /wallet/transaction/:id.
   352  func (srv *Server) walletTransactionHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   353  	// Parse the id from the url.
   354  	var id types.TransactionID
   355  	jsonID := "\"" + ps.ByName("id") + "\""
   356  	err := id.UnmarshalJSON([]byte(jsonID))
   357  	if err != nil {
   358  		writeError(w, "error after call to /wallet/history: "+err.Error(), http.StatusBadRequest)
   359  		return
   360  	}
   361  
   362  	txn, ok := srv.wallet.Transaction(id)
   363  	if !ok {
   364  		writeError(w, "error when calling /wallet/transaction/$(id): transaction not found", http.StatusBadRequest)
   365  		return
   366  	}
   367  	writeJSON(w, WalletTransactionGETid{
   368  		Transaction: txn,
   369  	})
   370  }
   371  
   372  // walletTransactionsHandler handles API calls to /wallet/transactions.
   373  func (srv *Server) walletTransactionsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   374  	// Get the start and end blocks.
   375  	start, err := strconv.Atoi(req.FormValue("startheight"))
   376  	if err != nil {
   377  		writeError(w, "error after call to /wallet/transactions: "+err.Error(), http.StatusBadRequest)
   378  		return
   379  	}
   380  	end, err := strconv.Atoi(req.FormValue("endheight"))
   381  	if err != nil {
   382  		writeError(w, "error after call to /wallet/transactions: "+err.Error(), http.StatusBadRequest)
   383  		return
   384  	}
   385  	confirmedTxns, err := srv.wallet.Transactions(types.BlockHeight(start), types.BlockHeight(end))
   386  	if err != nil {
   387  		writeError(w, "error after call to /wallet/transactions: "+err.Error(), http.StatusBadRequest)
   388  		return
   389  	}
   390  	unconfirmedTxns := srv.wallet.UnconfirmedTransactions()
   391  
   392  	writeJSON(w, WalletTransactionsGET{
   393  		ConfirmedTransactions:   confirmedTxns,
   394  		UnconfirmedTransactions: unconfirmedTxns,
   395  	})
   396  }
   397  
   398  // walletTransactionsAddrHandler handles API calls to
   399  // /wallet/transactions/:addr.
   400  func (srv *Server) walletTransactionsAddrHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   401  	// Parse the address being input.
   402  	jsonAddr := "\"" + ps.ByName("addr") + "\""
   403  	var addr types.UnlockHash
   404  	err := addr.UnmarshalJSON([]byte(jsonAddr))
   405  	if err != nil {
   406  		writeError(w, "error after call to /wallet/transactions: "+err.Error(), http.StatusBadRequest)
   407  		return
   408  	}
   409  
   410  	confirmedATs := srv.wallet.AddressTransactions(addr)
   411  	unconfirmedATs := srv.wallet.AddressUnconfirmedTransactions(addr)
   412  	writeJSON(w, WalletTransactionsGETaddr{
   413  		ConfirmedTransactions:   confirmedATs,
   414  		UnconfirmedTransactions: unconfirmedATs,
   415  	})
   416  }
   417  
   418  // walletUnlockHandler handles API calls to /wallet/unlock.
   419  func (srv *Server) walletUnlockHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   420  	potentialKeys := encryptionKeys(req.FormValue("encryptionpassword"))
   421  	for _, key := range potentialKeys {
   422  		err := srv.wallet.Unlock(key)
   423  		if err == nil {
   424  			writeSuccess(w)
   425  			return
   426  		}
   427  		if err != nil && err != modules.ErrBadEncryptionKey {
   428  			writeError(w, "error when calling /wallet/unlock: "+err.Error(), http.StatusBadRequest)
   429  			return
   430  		}
   431  	}
   432  	writeError(w, "error when calling /wallet/unlock: "+modules.ErrBadEncryptionKey.Error(), http.StatusBadRequest)
   433  }