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 }