github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/siac/walletcmd.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "math/big" 6 7 "github.com/bgentry/speakeasy" 8 "github.com/spf13/cobra" 9 10 "github.com/NebulousLabs/Sia/api" 11 "github.com/NebulousLabs/Sia/types" 12 ) 13 14 var ( 15 walletCmd = &cobra.Command{ 16 Use: "wallet", 17 Short: "Perform wallet actions", 18 Long: `Generate a new address, send coins to another wallet, or view info about the wallet. 19 20 Units: 21 The smallest unit of siacoins is the hasting. One siacoin is 10^24 hastings. Other supported units are: 22 pS (pico, 10^-12 SC) 23 nS (nano, 10^-9 SC) 24 uS (micro, 10^-6 SC) 25 mS (milli, 10^-3 SC) 26 SC 27 KS (kilo, 10^3 SC) 28 MS (mega, 10^6 SC) 29 GS (giga, 10^9 SC) 30 TS (tera, 10^12 SC)`, 31 Run: wrap(walletbalancecmd), 32 } 33 34 walletAddressCmd = &cobra.Command{ 35 Use: "address", 36 Short: "Get a new wallet address", 37 Long: "Generate a new wallet address.", 38 Run: wrap(walletaddresscmd), 39 } 40 41 walletAddressesCmd = &cobra.Command{ 42 Use: "addresses", 43 Short: "List all addresses", 44 Long: "List all addresses that have been generated by the wallet", 45 Run: wrap(walletaddressescmd), 46 } 47 48 walletInitCmd = &cobra.Command{ 49 Use: "init", 50 Short: "Initialize and encrypt a new wallet", 51 Long: `Generate a new wallet from a seed string, and encrypt it. 52 The seed string, which is also the encryption password, will be returned.`, 53 Run: wrap(walletinitcmd), 54 } 55 56 walletLoadCmd = &cobra.Command{ 57 Use: "load", 58 Short: "Load a wallet seed, v0.3.3.x wallet, or siag keyset", 59 Long: "Load a wallet seed, v0.3.3.x wallet, or siag keyset", 60 // Run field is not set, as the load command itself is not a valid command. 61 // A subcommand must be provided. 62 } 63 64 walletLoad033xCmd = &cobra.Command{ 65 Use: "033x [filepath]", 66 Short: "Load a v0.3.3.x wallet", 67 Long: "Load a v0.3.3.x wallet into the current wallet", 68 Run: wrap(walletload033xcmd), 69 } 70 71 walletLoadSeedCmd = &cobra.Command{ 72 Use: `seed`, 73 Short: "Add a seed to the wallet", 74 Long: "Uses the given password to create a new wallet with that as the primary seed", 75 Run: wrap(walletloadseedcmd), 76 } 77 78 walletLoadSiagCmd = &cobra.Command{ 79 Use: `siag [filepaths]`, 80 Short: "Load a siag keyset into the wallet", 81 Long: `Load a set of siag keys into the wallet - typically used for siafunds. 82 Example: 'siac wallet load siag key1.siakey,key2.siakey'`, 83 Run: wrap(walletloadsiagcmd), 84 } 85 86 walletLockCmd = &cobra.Command{ 87 Use: "lock", 88 Short: "Lock the wallet", 89 Long: "Lock the wallet, preventing further use", 90 Run: wrap(walletlockcmd), 91 } 92 93 walletSeedsCmd = &cobra.Command{ 94 Use: "seeds", 95 Short: "Retrieve information about your seeds", 96 Long: "Retrieves the current seed, how many addresses are remaining, and the rest of your seeds from the wallet", 97 Run: wrap(walletseedscmd), 98 } 99 100 walletSendCmd = &cobra.Command{ 101 Use: "send", 102 Short: "Send either siacoins or siafunds to an address", 103 Long: "Send either siacoins or siafunds to an address", 104 // Run field is not set, as the load command itself is not a valid command. 105 // A subcommand must be provided. 106 } 107 108 walletSendSiacoinsCmd = &cobra.Command{ 109 Use: "siacoins [amount] [dest]", 110 Short: "Send siacoins to an address", 111 Long: `Send siacoins to an address. 'dest' must be a 76-byte hexadecimal address. 112 'amount' can be specified in units, e.g. 1.23KS. Run 'wallet --help' for a list of units. 113 If no unit is supplied, hastings will be assumed. 114 115 A miner fee of 10 SC is levied on all transactions.`, 116 Run: wrap(walletsendsiacoinscmd), 117 } 118 119 walletSendSiafundsCmd = &cobra.Command{ 120 Use: "siafunds [amount] [dest]", 121 Short: "Send siafunds", 122 Long: `Send siafunds to an address, and transfer the claim siacoins to your wallet. 123 Run 'wallet send --help' to see a list of available units.`, 124 Run: wrap(walletsendsiafundscmd), 125 } 126 127 walletBalanceCmd = &cobra.Command{ 128 Use: "balance", 129 Short: "View wallet balance", 130 Long: "View wallet balance, including confirmed and unconfirmed siacoins and siafunds.", 131 Run: wrap(walletbalancecmd), 132 } 133 134 walletTransactionsCmd = &cobra.Command{ 135 Use: "transactions", 136 Short: "View transactions", 137 Long: "View transactions related to addresses spendable by the wallet, providing a net flow of siacoins and siafunds for each transaction", 138 Run: wrap(wallettransactionscmd), 139 } 140 141 walletUnlockCmd = &cobra.Command{ 142 Use: `unlock`, 143 Short: "Unlock the wallet", 144 Long: "Decrypt and load the wallet into memory", 145 Run: wrap(walletunlockcmd), 146 } 147 ) 148 149 // walletaddresscmd fetches a new address from the wallet that will be able to 150 // receive coins. 151 func walletaddresscmd() { 152 addr := new(api.WalletAddressGET) 153 err := getAPI("/wallet/address", addr) 154 if err != nil { 155 die("Could not generate new address:", err) 156 } 157 fmt.Printf("Created new address: %s\n", addr.Address) 158 } 159 160 // walletaddressescmd fetches the list of addresses that the wallet knows. 161 func walletaddressescmd() { 162 addrs := new(api.WalletAddressesGET) 163 err := getAPI("/wallet/addresses", addrs) 164 if err != nil { 165 die("Failed to fetch addresses:", err) 166 } 167 for _, addr := range addrs.Addresses { 168 fmt.Println(addr) 169 } 170 } 171 172 // walletinitcmd encrypts the wallet with the given password 173 func walletinitcmd() { 174 var er api.WalletInitPOST 175 qs := fmt.Sprintf("dictionary=%s", "english") 176 if initPassword { 177 password, err := speakeasy.Ask("Wallet password: ") 178 if err != nil { 179 die("Reading password failed:", err) 180 } 181 qs += fmt.Sprintf("&encryptionpassword=%s", password) 182 } 183 err := postResp("/wallet/init", qs, &er) 184 if err != nil { 185 die("Error when encrypting wallet:", err) 186 } 187 fmt.Printf("Seed is:\n %s\n\n", er.PrimarySeed) 188 if initPassword { 189 fmt.Printf("Wallet encrypted with given password\n") 190 } else { 191 fmt.Printf("Wallet encrypted with password: %s\n", er.PrimarySeed) 192 } 193 } 194 195 // walletload033xcmd loads a v0.3.3.x wallet into the current wallet. 196 func walletload033xcmd(source string) { 197 password, err := speakeasy.Ask("Wallet password: ") 198 if err != nil { 199 die("Reading password failed:", err) 200 } 201 qs := fmt.Sprintf("source=%s&encryptionpassword=%s", abs(source), password) 202 err = post("/wallet/033x", qs) 203 if err != nil { 204 die("Loading wallet failed:", err) 205 } 206 fmt.Println("Wallet loading successful.") 207 } 208 209 // walletloadseedcmd adds a seed to the wallet's list of seeds 210 func walletloadseedcmd() { 211 password, err := speakeasy.Ask("Wallet password: ") 212 if err != nil { 213 die("Reading password failed:", err) 214 } 215 seed, err := speakeasy.Ask("New Seed: ") 216 if err != nil { 217 die("Reading seed failed:", err) 218 } 219 qs := fmt.Sprintf("encryptionpassword=%s&seed=%s&dictionary=%s", password, seed, "english") 220 err = post("/wallet/seed", qs) 221 if err != nil { 222 die("Could not add seed:", err) 223 } 224 fmt.Println("Added Key") 225 } 226 227 // walletloadsiagcmd loads a siag key set into the wallet. 228 func walletloadsiagcmd(keyfiles string) { 229 password, err := speakeasy.Ask("Wallet password: ") 230 if err != nil { 231 die("Reading password failed:", err) 232 } 233 qs := fmt.Sprintf("keyfiles=%s&encryptionpassword=%s", keyfiles, password) 234 err = post("/wallet/siagkey", qs) 235 if err != nil { 236 die("Loading siag key failed:", err) 237 } 238 fmt.Println("Wallet loading successful.") 239 } 240 241 // walletlockcmd locks the wallet 242 func walletlockcmd() { 243 err := post("/wallet/lock", "") 244 if err != nil { 245 die("Could not lock wallet:", err) 246 } 247 } 248 249 // walletseedcmd returns the current seed { 250 func walletseedscmd() { 251 var seedInfo api.WalletSeedsGET 252 err := getAPI("/wallet/seeds", &seedInfo) 253 if err != nil { 254 die("Error retrieving the current seed:", err) 255 } 256 fmt.Printf("Primary Seed: %s\n"+ 257 "Addresses Remaining %d\n"+ 258 "All Seeds:\n", seedInfo.PrimarySeed, seedInfo.AddressesRemaining) 259 for _, seed := range seedInfo.AllSeeds { 260 fmt.Println(seed) 261 } 262 } 263 264 // walletsendsiacoinscmd sends siacoins to a destination address. 265 func walletsendsiacoinscmd(amount, dest string) { 266 hastings, err := parseCurrency(amount) 267 if err != nil { 268 die("Could not parse amount:", err) 269 } 270 err = post("/wallet/siacoins", fmt.Sprintf("amount=%s&destination=%s", hastings, dest)) 271 if err != nil { 272 die("Could not send siacoins:", err) 273 } 274 fmt.Printf("Sent %s hastings to %s\n", hastings, dest) 275 } 276 277 // walletsendsiafundscmd sends siafunds to a destination address. 278 func walletsendsiafundscmd(amount, dest string) { 279 err := post("/wallet/siafunds", fmt.Sprintf("amount=%s&destination=%s", amount, dest)) 280 if err != nil { 281 die("Could not send siafunds:", err) 282 } 283 fmt.Printf("Sent %s siafunds to %s\n", amount, dest) 284 } 285 286 // walletbalancecmd retrieves and displays information about the wallet. 287 func walletbalancecmd() { 288 status := new(api.WalletGET) 289 err := getAPI("/wallet", status) 290 if err != nil { 291 die("Could not get wallet status:", err) 292 } 293 encStatus := "Unencrypted" 294 if status.Encrypted { 295 encStatus = "Encrypted" 296 } 297 if !status.Unlocked { 298 fmt.Printf(`Wallet status: 299 %v, Locked 300 Unlock the wallet to view balance 301 `, encStatus) 302 return 303 } 304 305 unconfirmedBalance := status.ConfirmedSiacoinBalance.Add(status.UnconfirmedIncomingSiacoins).Sub(status.UnconfirmedOutgoingSiacoins) 306 var delta string 307 if unconfirmedBalance.Cmp(status.ConfirmedSiacoinBalance) >= 0 { 308 delta = "+" + currencyUnits(unconfirmedBalance.Sub(status.ConfirmedSiacoinBalance)) 309 } else { 310 delta = "-" + currencyUnits(status.ConfirmedSiacoinBalance.Sub(unconfirmedBalance)) 311 } 312 313 fmt.Printf(`Wallet status: 314 %s, Unlocked 315 Confirmed Balance: %v 316 Unconfirmed Delta: %v 317 Exact: %v H 318 Siafunds: %v SF 319 Siafund Claims: %v H 320 `, encStatus, currencyUnits(status.ConfirmedSiacoinBalance), delta, 321 status.ConfirmedSiacoinBalance, status.SiafundBalance, status.SiacoinClaimBalance) 322 } 323 324 // wallettransactionscmd lists all of the transactions related to the wallet, 325 // providing a net flow of siacoins and siafunds for each. 326 func wallettransactionscmd() { 327 wtg := new(api.WalletTransactionsGET) 328 err := getAPI("/wallet/transactions?startheight=0&endheight=10000000", wtg) 329 if err != nil { 330 die("Could not fetch transaction history:", err) 331 } 332 333 fmt.Println(" [height] [transaction id] [net siacoins] [net siafunds]") 334 txns := append(wtg.ConfirmedTransactions, wtg.UnconfirmedTransactions...) 335 for _, txn := range txns { 336 // Determine the number of outgoing siacoins and siafunds. 337 var outgoingSiacoins types.Currency 338 var outgoingSiafunds types.Currency 339 for _, input := range txn.Inputs { 340 if input.FundType == types.SpecifierSiacoinInput && input.WalletAddress { 341 outgoingSiacoins = outgoingSiacoins.Add(input.Value) 342 } 343 if input.FundType == types.SpecifierSiafundInput && input.WalletAddress { 344 outgoingSiafunds = outgoingSiafunds.Add(input.Value) 345 } 346 } 347 348 // Determine the number of incoming siacoins and siafunds. 349 var incomingSiacoins types.Currency 350 var incomingSiafunds types.Currency 351 for _, output := range txn.Outputs { 352 if output.FundType == types.SpecifierMinerPayout { 353 incomingSiacoins = incomingSiacoins.Add(output.Value) 354 } 355 if output.FundType == types.SpecifierSiacoinOutput && output.WalletAddress { 356 incomingSiacoins = incomingSiacoins.Add(output.Value) 357 } 358 if output.FundType == types.SpecifierSiafundOutput && output.WalletAddress { 359 incomingSiafunds = incomingSiafunds.Add(output.Value) 360 } 361 } 362 363 // Convert the siacoins to a float. 364 incomingSiacoinsFloat, _ := new(big.Rat).SetFrac(incomingSiacoins.Big(), types.SiacoinPrecision.Big()).Float64() 365 outgoingSiacoinsFloat, _ := new(big.Rat).SetFrac(outgoingSiacoins.Big(), types.SiacoinPrecision.Big()).Float64() 366 367 // Print the results. 368 if txn.ConfirmationHeight < 1e9 { 369 fmt.Printf("%12v", txn.ConfirmationHeight) 370 } else { 371 fmt.Printf(" unconfirmed") 372 } 373 fmt.Printf("%67v%15.2f SC", txn.TransactionID, incomingSiacoinsFloat-outgoingSiacoinsFloat) 374 // For siafunds, need to avoid having a negative types.Currency. 375 if incomingSiafunds.Cmp(outgoingSiafunds) >= 0 { 376 fmt.Printf("%14v SF\n", incomingSiafunds.Sub(outgoingSiafunds)) 377 } else { 378 fmt.Printf("-%14v SF\n", outgoingSiafunds.Sub(incomingSiafunds)) 379 } 380 } 381 } 382 383 // walletunlockcmd unlocks a saved wallet 384 func walletunlockcmd() { 385 password, err := speakeasy.Ask("Wallet password: ") 386 if err != nil { 387 die("Reading password failed:", err) 388 } 389 qs := fmt.Sprintf("encryptionpassword=%s&dictonary=%s", password, "english") 390 err = post("/wallet/unlock", qs) 391 if err != nil { 392 die("Could not unlock wallet:", err) 393 } 394 fmt.Println("Wallet unlocked") 395 }