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  }