github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/cmd/dero-wallet-cli/prompt.go (about)

     1  // Copyright 2017-2018 DERO Project. All rights reserved.
     2  // Use of this source code in any form is governed by RESEARCH license.
     3  // license can be found in the LICENSE file.
     4  // GPG: 0F39 E425 8C65 3947 702A  8234 08B2 0360 A03A 9DE8
     5  //
     6  //
     7  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
     8  // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     9  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
    10  // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    11  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    12  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    13  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    14  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
    15  // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    16  
    17  package main
    18  
    19  import "os"
    20  import "io"
    21  import "fmt"
    22  import "bytes"
    23  import "time"
    24  import "io/ioutil"
    25  import "path/filepath"
    26  import "strings"
    27  import "strconv"
    28  import "encoding/hex"
    29  
    30  import "github.com/chzyer/readline"
    31  
    32  import "github.com/deroproject/derosuite/config"
    33  import "github.com/deroproject/derosuite/crypto"
    34  import "github.com/deroproject/derosuite/globals"
    35  import "github.com/deroproject/derosuite/address"
    36  import "github.com/deroproject/derosuite/walletapi"
    37  
    38  var account walletapi.Account
    39  
    40  // handle all commands while  in prompt mode
    41  func handle_prompt_command(l *readline.Instance, line string) {
    42  
    43  	var err error
    44  	line = strings.TrimSpace(line)
    45  	line_parts := strings.Fields(line)
    46  
    47  	if len(line_parts) < 1 { // if no command return
    48  		return
    49  	}
    50  
    51  	_ = err
    52  	command := ""
    53  	if len(line_parts) >= 1 {
    54  		command = strings.ToLower(line_parts[0])
    55  	}
    56  
    57  	// handled closed wallet commands
    58  	switch command {
    59  	case "address", "rescan_bc", "rescan_spent", "seed", "set", "password", "get_tx_key", "i8", "i32", "payment_id":
    60  		fallthrough
    61  	case "spendkey", "viewkey", "transfer", "locked_transfer", "close":
    62  		fallthrough
    63  	case "transfer_all", "sweep_all", "show_transfers", "balance", "status":
    64  		if wallet == nil {
    65  			globals.Logger.Warnf("No wallet available")
    66  			return
    67  		}
    68  	}
    69  
    70  	locked_to_height := uint64(0)
    71  	switch command {
    72  	case "help":
    73  		usage(l.Stderr())
    74  	case "address": // give user his account address
    75  
    76  		fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+"\n", wallet.GetAddress())
    77  	case "status": // show syncronisation status
    78  		fmt.Fprintf(l.Stderr(), "Wallet Version : %s\n", config.Version.String())
    79  		fmt.Fprintf(l.Stderr(), "Wallet Height : %d\t Daemon Height %d \n", wallet.Get_Height(), wallet.Get_Daemon_Height())
    80  		fallthrough
    81  	case "balance": // give user his balance
    82  		balance_unlocked, locked_balance := wallet.Get_Balance_Rescan()
    83  		fmt.Fprintf(l.Stderr(), "Locked balance   : "+color_green+"%s"+color_white+"\n", globals.FormatMoney12(locked_balance))
    84  		fmt.Fprintf(l.Stderr(), "Unlocked balance : "+color_green+"%s"+color_white+"\n", globals.FormatMoney12(balance_unlocked))
    85  		fmt.Fprintf(l.Stderr(), "Total balance    : "+color_green+"%s"+color_white+"\n\n", globals.FormatMoney12(locked_balance+balance_unlocked))
    86  
    87  	case "rescan_bc", "rescan_spent": // rescan from 0
    88  		if offline_mode {
    89  			globals.Logger.Warnf("Offline wallet rescanning NOT implemented")
    90  		} else {
    91  			rescan_bc(wallet)
    92  		}
    93  
    94  	case "seed": // give user his seed, if password is valid
    95  		if !wallet.Is_View_Only() {
    96  			if !ValidateCurrentPassword(l, wallet) {
    97  				globals.Logger.Warnf("Invalid password")
    98  				PressAnyKey(l, wallet)
    99  				break
   100  			}
   101  			display_seed(l, wallet) // seed should be given only to authenticated users
   102  		} else {
   103  			fmt.Fprintf(l.Stderr(), color_red+" View wallet do not have seeds"+color_white)
   104  		}
   105  	case "spendkey": // give user his spend key
   106  		if wallet.Is_View_Only() {
   107  			fmt.Fprintf(l.Stderr(), "View wallet do not have spend key")
   108  			return
   109  		}
   110  
   111  		display_spend_key(l, wallet)
   112  	case "viewkey": // give user his viewkey
   113  		if !ValidateCurrentPassword(l, wallet) {
   114  			globals.Logger.Warnf("Invalid password")
   115  			break
   116  		}
   117  		display_view_key(l, wallet)
   118  	case "walletviewkey":
   119  		if !ValidateCurrentPassword(l, wallet) {
   120  			globals.Logger.Warnf("Invalid password")
   121  			break
   122  		}
   123  		display_viewwallet_key(l, wallet)
   124  
   125  	case "password": // change wallet password
   126  		if ConfirmYesNoDefaultNo(l, "Change wallet password (y/N)") &&
   127  			ValidateCurrentPassword(l, wallet) {
   128  
   129  			new_password := ReadConfirmedPassword(l, "Enter new password", "Confirm password")
   130  			err = wallet.Set_Encrypted_Wallet_Password(new_password)
   131  			if err == nil {
   132  				globals.Logger.Infof("Wallet password successfully changed")
   133  			} else {
   134  				globals.Logger.Warnf("Wallet password could not be changed err %s", err)
   135  			}
   136  		}
   137  
   138  	case "get_tx_key":
   139  		if len(line_parts) == 2 && len(line_parts[1]) == 64 {
   140  			_, err := hex.DecodeString(line_parts[1])
   141  			if err != nil {
   142  				globals.Logger.Warnf("Error parsing txhash")
   143  				break
   144  			}
   145  			key := wallet.GetTXKey(crypto.HexToHash(line_parts[1]))
   146  			if key != "" {
   147  				globals.Logger.Infof("TX secret key \"%s\"", key)
   148  			} else {
   149  				globals.Logger.Warnf("TX not found in database")
   150  			}
   151  		} else {
   152  			globals.Logger.Warnf("get_tx_key needs transaction hash as input parameter")
   153  			globals.Logger.Warnf("eg. get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7")
   154  		}
   155  	case "sweep_all", "transfer_all": // transfer everything
   156  		Transfer_Everything(l)
   157  
   158  	case "show_transfers":
   159  		show_transfers(l, wallet, 100)
   160  	case "set": // set/display different settings
   161  		handle_set_command(l, line)
   162  	case "close": // close the account
   163  		if !ValidateCurrentPassword(l, wallet) {
   164  			globals.Logger.Warnf("Invalid password")
   165  			break
   166  		}
   167  		wallet.Close_Encrypted_Wallet() // overwrite previous instance
   168  
   169  	case "menu": // enable menu mode
   170  		menu_mode = true
   171  		globals.Logger.Infof("Menu mode enabled")
   172  	case "i8", "integrated_address": // user wants a random integrated address 8 bytes
   173  		a := wallet.GetRandomIAddress8()
   174  		fmt.Fprintf(l.Stderr(), "Wallet integrated address : "+color_green+"%s"+color_white+"\n", a.String())
   175  		fmt.Fprintf(l.Stderr(), "Embedded payment ID : "+color_green+"%x"+color_white+"\n", a.PaymentID)
   176  
   177  	case "i32", "integrated_address32":
   178  		a := wallet.GetRandomIAddress32()
   179  		fmt.Fprintf(l.Stderr(), "Wallet integrated address : "+color_green+"%s"+color_white+"\n", a.String())
   180  		fmt.Fprintf(l.Stderr(), "Embedded 32 byte payment ID : "+color_green+"%x"+color_white+"\n", a.PaymentID)
   181  	case "payment_id":
   182  		a := wallet.GetRandomIAddress32()
   183  		fmt.Fprintf(l.Stderr(), "Random 32 byte payment ID : "+color_green+"%x"+color_white+"\n", a.PaymentID)
   184  
   185  	case "version":
   186  		globals.Logger.Infof("Version %s\n", config.Version.String())
   187  	case "locked_transfer": // parse locked to height
   188  		_ = locked_to_height
   189  
   190  	case "transfer":
   191  		// parse the address, amount pair
   192  		line_parts := line_parts[1:] // remove first part
   193  
   194  		addr_list := []address.Address{}
   195  		amount_list := []uint64{}
   196  		payment_id := ""
   197  
   198  		for i := 0; i < len(line_parts); {
   199  
   200  			globals.Logger.Debugf("len %d %+v", len(line_parts), line_parts)
   201  			if len(line_parts) >= 2 { // parse address amount pair
   202  				addr, err := globals.ParseValidateAddress(line_parts[0])
   203  				if err != nil {
   204  					globals.Logger.Warnf("Error Parsing \"%s\" err %s", line_parts[0], err)
   205  					return
   206  				}
   207  				amount, err := globals.ParseAmount(line_parts[1])
   208  				if err != nil {
   209  					globals.Logger.Warnf("Error Parsing \"%s\" err %s", line_parts[1], err)
   210  					return
   211  				}
   212  				line_parts = line_parts[2:] // remove parsed
   213  
   214  				addr_list = append(addr_list, *addr)
   215  				amount_list = append(amount_list, amount)
   216  
   217  				continue
   218  			}
   219  			if len(line_parts) == 1 { // parse payment_id
   220  				if len(line_parts[0]) == 64 || len(line_parts[0]) == 16 {
   221  					_, err := hex.DecodeString(line_parts[0])
   222  					if err != nil {
   223  						globals.Logger.Warnf("Error parsing payment ID, it should be in hex 16 or 64 chars")
   224  						return
   225  					}
   226  					payment_id = line_parts[0]
   227  					line_parts = line_parts[1:]
   228  
   229  				} else {
   230  					globals.Logger.Warnf("Invalid payment ID \"%s\"", line_parts[0])
   231  					return
   232  				}
   233  
   234  			}
   235  		}
   236  
   237  		// check if everything is okay, if yes build the transaction
   238  		if len(addr_list) == 0 {
   239  			globals.Logger.Warnf("Destination address not provided")
   240  			return
   241  		}
   242  
   243  		payment_id_integrated := false
   244  		for i := range addr_list {
   245  			if addr_list[i].IsIntegratedAddress() {
   246  				payment_id_integrated = true
   247  				globals.Logger.Infof("Payment ID is integreted in address ID:%x", addr_list[i].PaymentID)
   248  			}
   249  
   250  		}
   251  
   252  		// if user provided an integrated address donot ask him payment id
   253  		// otherwise confirm whether user wants to send without payment id
   254  		if payment_id_integrated == false && len(payment_id) == 0 {
   255  			payment_id_bytes, err := ReadPaymentID(l)
   256  			payment_id = hex.EncodeToString(payment_id_bytes)
   257  			if err != nil {
   258  				globals.Logger.Warnf("Err :%s", err)
   259  				break
   260  			}
   261  		}
   262  
   263  		offline := false
   264  		tx, inputs, input_sum, change, err := wallet.Transfer(addr_list, amount_list, 0, payment_id, 0, 0)
   265  		build_relay_transaction(l, tx, inputs, input_sum, change, err, offline, amount_list)
   266  
   267  	case "q", "bye", "exit", "quit":
   268  		globals.Exit_In_Progress = true
   269  		if wallet != nil {
   270  			wallet.Close_Encrypted_Wallet() // overwrite previous instance
   271  		}
   272  
   273  	case "": // blank enter key just loop
   274  	default:
   275  		//fmt.Fprintf(l.Stderr(), "you said: %s", strconv.Quote(line))
   276  		globals.Logger.Warnf("No such command")
   277  	}
   278  
   279  }
   280  
   281  // handle all commands while  in prompt mode
   282  func handle_set_command(l *readline.Instance, line string) {
   283  
   284  	//var err error
   285  	line = strings.TrimSpace(line)
   286  	line_parts := strings.Fields(line)
   287  
   288  	if len(line_parts) < 1 { // if no command return
   289  		return
   290  	}
   291  
   292  	command := ""
   293  	if len(line_parts) >= 2 {
   294  		command = strings.ToLower(line_parts[1])
   295  	}
   296  
   297  	help := false
   298  	switch command {
   299  	case "help":
   300  	case "mixin":
   301  		if len(line_parts) != 3 {
   302  			globals.Logger.Warnf("Wrong number of arguments, see help eg")
   303  			help = true
   304  			break
   305  		}
   306  		s, err := strconv.ParseUint(line_parts[2], 10, 64)
   307  		if err != nil {
   308  			globals.Logger.Warnf("Error parsing mixin")
   309  			return
   310  		}
   311  		wallet.SetMixin(int(s))
   312  		globals.Logger.Infof("Mixin =  %d", wallet.GetMixin())
   313  
   314  	case "priority":
   315  		if len(line_parts) != 3 {
   316  			globals.Logger.Warnf("Wrong number of arguments, see help eg")
   317  			help = true
   318  			break
   319  		}
   320  		s, err := strconv.ParseFloat(line_parts[2], 64)
   321  		if err != nil {
   322  			globals.Logger.Warnf("Error parsing priority")
   323  			return
   324  		}
   325  		wallet.SetFeeMultiplier(float32(s))
   326  		globals.Logger.Infof("Transaction priority =  %.02f", wallet.GetFeeMultiplier())
   327  
   328  	case "seed": // seed only has 1 setting, lanuage so do it now
   329  		language := choose_seed_language(l)
   330  		globals.Logger.Infof("Setting seed language to  \"%s\"", wallet.SetSeedLanguage(language))
   331  
   332  	default:
   333  		help = true
   334  	}
   335  
   336  	if help == true || len(line_parts) == 1 { // user type plain set command, give out all settings and help
   337  
   338  		fmt.Fprintf(l.Stderr(), color_extra_white+"Current settings"+color_extra_white+"\n")
   339  		fmt.Fprintf(l.Stderr(), color_normal+"Seed Language: "+color_extra_white+"%s\t"+color_normal+"eg. "+color_extra_white+"set seed language\n"+color_normal, wallet.GetSeedLanguage())
   340  		fmt.Fprintf(l.Stderr(), color_normal+"Mixin: "+color_extra_white+"%d\t"+color_normal+"eg. "+color_extra_white+"set mixin 13\n"+color_normal, wallet.GetMixin())
   341  		fmt.Fprintf(l.Stderr(), color_normal+"Priority: "+color_extra_white+"%0.2f\t"+color_normal+"eg. "+color_extra_white+"set priority 4.0\t"+color_normal+"Transaction priority on DERO network \n", wallet.GetFeeMultiplier())
   342  		fmt.Fprintf(l.Stderr(), "\t\tMinimum priority is 1.00. High priority = high fees\n")
   343  
   344  	}
   345  }
   346  
   347  func Transfer_Everything(l *readline.Instance) {
   348  	if wallet.Is_View_Only() {
   349  		fmt.Fprintf(l.Stderr(), color_yellow+"View Only wallet cannot transfer."+color_white)
   350  	}
   351  
   352  	if !ValidateCurrentPassword(l, wallet) {
   353  		globals.Logger.Warnf("Invalid password")
   354  		return
   355  	}
   356  
   357  	// a , amount_to_transfer, err := collect_transfer_info(l,wallet)
   358  	addr, err := ReadAddress(l)
   359  	if err != nil {
   360  		globals.Logger.Warnf("Err :%s", err)
   361  		return
   362  	}
   363  
   364  	var payment_id []byte
   365  	// if user provided an integrated address donot ask him payment id
   366  	if !addr.IsIntegratedAddress() {
   367  		payment_id, err = ReadPaymentID(l)
   368  		if err != nil {
   369  			globals.Logger.Warnf("Err :%s", err)
   370  			return
   371  		}
   372  	} else {
   373  		globals.Logger.Infof("Payment ID is integreted in address ID:%x", addr.PaymentID)
   374  	}
   375  
   376  	fees_per_kb := uint64(0) // fees  must be calculated by walletapi
   377  
   378  	tx, inputs, input_sum, err := wallet.Transfer_Everything(*addr, hex.EncodeToString(payment_id), 0, fees_per_kb, 5)
   379  
   380  	_ = inputs
   381  	if err != nil {
   382  		globals.Logger.Warnf("Error while building Transaction err %s\n", err)
   383  		return
   384  	}
   385  	globals.Logger.Infof("%d Inputs Selected for %s DERO", len(inputs), globals.FormatMoney12(input_sum))
   386  	globals.Logger.Infof("fees %s DERO", globals.FormatMoneyPrecision(tx.RctSignature.Get_TX_Fee(), 12))
   387  	globals.Logger.Infof("TX Size %0.1f KiB (should be  < 240 KiB)", float32(len(tx.Serialize()))/1024.0)
   388  	offline_tx := false
   389  	if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") {
   390  
   391  		if offline_tx { // if its an offline tx, dump it to a file
   392  			cur_dir, err := os.Getwd()
   393  			if err != nil {
   394  				globals.Logger.Warnf("Cannot obtain current directory to save tx")
   395  				return
   396  			}
   397  			filename := filepath.Join(cur_dir, tx.GetHash().String()+".tx")
   398  			err = ioutil.WriteFile(filename, []byte(hex.EncodeToString(tx.Serialize())), 0600)
   399  			if err == nil {
   400  				if err == nil {
   401  					globals.Logger.Infof("Transaction saved successfully. txid = %s", tx.GetHash())
   402  					globals.Logger.Infof("Saved to %s", filename)
   403  				} else {
   404  					globals.Logger.Warnf("Error saving tx to %s , err %s", filename, err)
   405  				}
   406  			}
   407  
   408  		} else {
   409  
   410  			err = wallet.SendTransaction(tx) // relay tx to daemon/network
   411  			if err == nil {
   412  				globals.Logger.Infof("Transaction sent successfully. txid = %s", tx.GetHash())
   413  			} else {
   414  				globals.Logger.Warnf("Transaction sending failed txid = %s, err %s", tx.GetHash(), err)
   415  			}
   416  
   417  		}
   418  	}
   419  
   420  }
   421  
   422  // read an address with all goodies such as color encoding and other things in prompt
   423  func ReadAddress(l *readline.Instance) (a *address.Address, err error) {
   424  	setPasswordCfg := l.GenPasswordConfig()
   425  	setPasswordCfg.EnableMask = false
   426  
   427  	prompt_mutex.Lock()
   428  	defer prompt_mutex.Unlock()
   429  
   430  	setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
   431  		error_message := ""
   432  		color := color_green
   433  
   434  		if len(line) >= 1 {
   435  			_, err := globals.ParseValidateAddress(string(line))
   436  			if err != nil {
   437  				error_message = " " //err.Error()
   438  			}
   439  		}
   440  
   441  		if error_message != "" {
   442  			color = color_red // Should we display the error message here??
   443  			l.SetPrompt(fmt.Sprintf("%sEnter Destination Address: ", color))
   444  		} else {
   445  			l.SetPrompt(fmt.Sprintf("%sEnter Destination Address: ", color))
   446  
   447  		}
   448  
   449  		l.Refresh()
   450  		return nil, 0, false
   451  	})
   452  
   453  	line, err := l.ReadPasswordWithConfig(setPasswordCfg)
   454  	if err != nil {
   455  		return
   456  	}
   457  	a, err = globals.ParseValidateAddress(string(line))
   458  	l.SetPrompt(prompt)
   459  	l.Refresh()
   460  	return
   461  }
   462  
   463  // read an payment with all goodies such as color encoding and other things in prompt
   464  func ReadPaymentID(l *readline.Instance) (payment_id []byte, err error) {
   465  	setPasswordCfg := l.GenPasswordConfig()
   466  	setPasswordCfg.EnableMask = false
   467  
   468  	// ask user whether he want to enter a payment ID
   469  
   470  	if !ConfirmYesNoDefaultNo(l, "Provide Payment ID (y/N)") { // user doesnot want to provide payment it, skip
   471  		return
   472  	}
   473  
   474  	prompt_mutex.Lock()
   475  	defer prompt_mutex.Unlock()
   476  
   477  	setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
   478  		error_message := ""
   479  		color := color_green
   480  
   481  		if len(line) >= 1 {
   482  			_, err := hex.DecodeString(string(line))
   483  			if (len(line) == 16 || len(line) == 64) && err == nil {
   484  				error_message = ""
   485  			} else {
   486  				error_message = " " //err.Error()
   487  			}
   488  		}
   489  
   490  		if error_message != "" {
   491  			color = color_red // Should we display the error message here??
   492  			l.SetPrompt(fmt.Sprintf("%sEnter Payment ID (16/64 hex char): ", color))
   493  		} else {
   494  			l.SetPrompt(fmt.Sprintf("%sEnter Payment ID (16/64 hex char): ", color))
   495  
   496  		}
   497  
   498  		l.Refresh()
   499  		return nil, 0, false
   500  	})
   501  
   502  	line, err := l.ReadPasswordWithConfig(setPasswordCfg)
   503  	if err != nil {
   504  		return
   505  	}
   506  	payment_id, err = hex.DecodeString(string(line))
   507  	if err != nil {
   508  		return
   509  	}
   510  	l.SetPrompt(prompt)
   511  	l.Refresh()
   512  
   513  	if len(payment_id) == 8 || len(payment_id) == 32 {
   514  		return
   515  	}
   516  
   517  	err = fmt.Errorf("Invalid Payment ID")
   518  	return
   519  }
   520  
   521  // confirms whether the user wants to confirm yes
   522  func ConfirmYesNoDefaultYes(l *readline.Instance, prompt_temporary string) bool {
   523  	prompt_mutex.Lock()
   524  	defer prompt_mutex.Unlock()
   525  
   526  	l.SetPrompt(prompt_temporary)
   527  	line, err := l.Readline()
   528  	if err == readline.ErrInterrupt {
   529  		if len(line) == 0 {
   530  			globals.Logger.Infof("Ctrl-C received, Exiting\n")
   531  			os.Exit(0)
   532  		}
   533  	} else if err == io.EOF {
   534  		os.Exit(0)
   535  	}
   536  	l.SetPrompt(prompt)
   537  	l.Refresh()
   538  
   539  	if strings.TrimSpace(line) == "n" || strings.TrimSpace(line) == "N" {
   540  		return false
   541  	}
   542  	return true
   543  }
   544  
   545  // confirms whether the user wants to confirm NO
   546  func ConfirmYesNoDefaultNo(l *readline.Instance, prompt_temporary string) bool {
   547  	prompt_mutex.Lock()
   548  	defer prompt_mutex.Unlock()
   549  
   550  	l.SetPrompt(prompt_temporary)
   551  	line, err := l.Readline()
   552  	if err == readline.ErrInterrupt {
   553  		if len(line) == 0 {
   554  			globals.Logger.Infof("Ctrl-C received, Exiting\n")
   555  			os.Exit(0)
   556  		}
   557  	} else if err == io.EOF {
   558  		os.Exit(0)
   559  	}
   560  	l.SetPrompt(prompt)
   561  
   562  	if strings.TrimSpace(line) == "y" || strings.TrimSpace(line) == "Y" {
   563  		return true
   564  	}
   565  	return false
   566  }
   567  
   568  // confirms whether user knows the current password for the wallet
   569  // this is triggerred while transferring  amount, changing settings and so on
   570  func ValidateCurrentPassword(l *readline.Instance, wallet *walletapi.Wallet) bool {
   571  	prompt_mutex.Lock()
   572  	defer prompt_mutex.Unlock()
   573  
   574  	// if user requested wallet to be open/unlocked, keep it open
   575  	if globals.Arguments["--unlocked"].(bool) == true {
   576  		return true
   577  	}
   578  
   579  	setPasswordCfg := l.GenPasswordConfig()
   580  	setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
   581  		l.SetPrompt(fmt.Sprintf("Enter current wallet password(%v): ", len(line)))
   582  		l.Refresh()
   583  		return nil, 0, false
   584  	})
   585  
   586  	//pswd, err := l.ReadPassword("please enter your password: ")
   587  	pswd, err := l.ReadPasswordWithConfig(setPasswordCfg)
   588  	if err != nil {
   589  		return false
   590  	}
   591  
   592  	// something was read, check whether it's the password setup in the wallet
   593  	return wallet.Check_Password(string(pswd))
   594  }
   595  
   596  // reads a password to open the wallet
   597  func ReadPassword(l *readline.Instance, filename string) string {
   598  	prompt_mutex.Lock()
   599  	defer prompt_mutex.Unlock()
   600  
   601  try_again:
   602  	setPasswordCfg := l.GenPasswordConfig()
   603  	setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
   604  		l.SetPrompt(fmt.Sprintf("Enter wallet password for %s (%v): ", filename, len(line)))
   605  		l.Refresh()
   606  		return nil, 0, false
   607  	})
   608  
   609  	//pswd, err := l.ReadPassword("please enter your password: ")
   610  	pswd, err := l.ReadPasswordWithConfig(setPasswordCfg)
   611  	if err != nil {
   612  		goto try_again
   613  	}
   614  
   615  	// something was read, check whether it's the password setup in the wallet
   616  	return string(pswd)
   617  }
   618  func ReadConfirmedPassword(l *readline.Instance, first_prompt string, second_prompt string) (password string) {
   619  	prompt_mutex.Lock()
   620  	defer prompt_mutex.Unlock()
   621  
   622  	for {
   623  		setPasswordCfg := l.GenPasswordConfig()
   624  		setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
   625  			l.SetPrompt(fmt.Sprintf("%s(%v): ", first_prompt, len(line)))
   626  			l.Refresh()
   627  			return nil, 0, false
   628  		})
   629  
   630  		password_bytes, err := l.ReadPasswordWithConfig(setPasswordCfg)
   631  		if err != nil {
   632  			//return
   633  			continue
   634  		}
   635  
   636  		setPasswordCfg = l.GenPasswordConfig()
   637  		setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
   638  			l.SetPrompt(fmt.Sprintf("%s(%v): ", second_prompt, len(line)))
   639  			l.Refresh()
   640  			return nil, 0, false
   641  		})
   642  
   643  		confirmed_bytes, err := l.ReadPasswordWithConfig(setPasswordCfg)
   644  		if err != nil {
   645  			//return
   646  			continue
   647  		}
   648  
   649  		if bytes.Equal(password_bytes, confirmed_bytes) {
   650  			password = string(password_bytes)
   651  			err = nil
   652  			return
   653  		}
   654  
   655  		globals.Logger.Warnf("Passwords mismatch.Retrying.")
   656  	}
   657  
   658  }
   659  
   660  // confirms  user to press a key
   661  // this is triggerred while transferring  amount, changing settings and so on
   662  func PressAnyKey(l *readline.Instance, wallet *walletapi.Wallet) {
   663  
   664  	prompt_mutex.Lock()
   665  	defer prompt_mutex.Unlock()
   666  
   667  	setPasswordCfg := l.GenPasswordConfig()
   668  	setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
   669  
   670  		l.SetPrompt(fmt.Sprintf("Press ENTER key to continue..."))
   671  		l.Refresh()
   672  
   673  		return nil, 0, false
   674  	})
   675  
   676  	// any error or any key is the same
   677  	l.ReadPasswordWithConfig(setPasswordCfg)
   678  
   679  	return
   680  }
   681  
   682  /*
   683  // if we are in offline, scan default or user provided file
   684  // this function will replay the blockchain data in offline mode
   685  func trigger_offline_data_scan() {
   686  	filename := default_offline_datafile
   687  
   688  	if globals.Arguments["--offline_datafile"] != nil {
   689  		filename = globals.Arguments["--offline_datafile"].(string)
   690  	}
   691  
   692  	f, err := os.Open(filename)
   693  	if err != nil {
   694  		globals.Logger.Warnf("Cannot read offline data file=\"%s\"  err: %s   ", filename, err)
   695  		return
   696  	}
   697  	w := bufio.NewReader(f)
   698  	gzipreader, err := gzip.NewReader(w)
   699  	if err != nil {
   700  		globals.Logger.Warnf("Error while decompressing offline data file=\"%s\"  err: %s   ", filename, err)
   701  		return
   702  	}
   703  	defer gzipreader.Close()
   704  	io.Copy(pipe_writer, gzipreader)
   705  }
   706  */
   707  
   708  // this completer is used to complete the commands at the prompt
   709  // BUG, this needs to be disabled in menu mode
   710  var completer = readline.NewPrefixCompleter(
   711  	readline.PcItem("help"),
   712  	readline.PcItem("address"),
   713  	readline.PcItem("balance"),
   714  	readline.PcItem("integrated_address"),
   715  	readline.PcItem("get_tx_key"),
   716  	readline.PcItem("menu"),
   717  	readline.PcItem("rescan_bc"),
   718  	readline.PcItem("rescan_spent"),
   719  	readline.PcItem("payment_id"),
   720  	readline.PcItem("print_height"),
   721  	readline.PcItem("seed"),
   722  
   723  	readline.PcItem("set",
   724  		readline.PcItem("mixin"),
   725  		readline.PcItem("seed"),
   726  		readline.PcItem("priority"),
   727  	),
   728  	readline.PcItem("show_transfers"),
   729  	readline.PcItem("spendkey"),
   730  	readline.PcItem("status"),
   731  	readline.PcItem("viewkey"),
   732  	readline.PcItem("version"),
   733  	readline.PcItem("transfer"),
   734  	readline.PcItem("transfer_all"),
   735  	readline.PcItem("walletviewkey"),
   736  	readline.PcItem("bye"),
   737  	readline.PcItem("exit"),
   738  	readline.PcItem("quit"),
   739  )
   740  
   741  // help command screen
   742  func usage(w io.Writer) {
   743  	io.WriteString(w, "commands:\n")
   744  	io.WriteString(w, "\t\033[1mhelp\033[0m\t\tthis help\n")
   745  	io.WriteString(w, "\t\033[1maddress\033[0m\t\tDisplay user address\n")
   746  	io.WriteString(w, "\t\033[1mbalance\033[0m\t\tDisplay user balance\n")
   747  	io.WriteString(w, "\t\033[1mget_tx_key\033[0m\tDisplay tx secret key for specific transaction\n")
   748  	io.WriteString(w, "\t\033[1mintegrated_address\033[0m\tDisplay random integrated address (with encrypted payment ID)\n")
   749  	io.WriteString(w, "\t\033[1mmenu\033[0m\t\tEnable menu mode\n")
   750  	io.WriteString(w, "\t\033[1mrescan_bc\033[0m\tRescan blockchain again from 0 height\n")
   751  	io.WriteString(w, "\t\033[1mpassword\033[0m\tChange wallet password\n")
   752  	io.WriteString(w, "\t\033[1mpayment_id\033[0m\tPrint random Payment ID (for encrypted version see integrated_address)\n")
   753  	io.WriteString(w, "\t\033[1mseed\033[0m\t\tDisplay seed\n")
   754  	io.WriteString(w, "\t\033[1mshow_transfers\033[0m\tShow all transactions to/from current wallet\n")
   755  	io.WriteString(w, "\t\033[1mset\033[0m\t\tSet/get various settings\n")
   756  	io.WriteString(w, "\t\033[1mstatus\033[0m\t\tShow general information and balance\n")
   757  	io.WriteString(w, "\t\033[1mspendkey\033[0m\tView secret key\n")
   758  	io.WriteString(w, "\t\033[1mtransfer\033[0m\tTransfer/Send DERO to another address\n")
   759  	io.WriteString(w, "\t\t\tEg. transfer <address> <amount> [ <address2> <amount2> ]... [<payment_id>] \n")
   760  	io.WriteString(w, "\t\033[1mtransfer_all\033[0m\tTransfer everything to another address\n")
   761  	io.WriteString(w, "\t\033[1mviewkey\033[0m\t\tView view key\n")
   762  	io.WriteString(w, "\t\033[1mwalletviewkey\033[0m\tWallet view key, used to create watchable view only wallet\n")
   763  	io.WriteString(w, "\t\033[1mversion\033[0m\t\tShow version\n")
   764  	io.WriteString(w, "\t\033[1mbye\033[0m\t\tQuit wallet\n")
   765  	io.WriteString(w, "\t\033[1mexit\033[0m\t\tQuit wallet\n")
   766  	io.WriteString(w, "\t\033[1mquit\033[0m\t\tQuit wallet\n")
   767  
   768  }
   769  
   770  // display seed to the user in his preferred language
   771  func display_seed(l *readline.Instance, wallet *walletapi.Wallet) {
   772  	seed := wallet.GetSeed()
   773  	fmt.Fprintf(l.Stderr(), color_green+"PLEASE NOTE: the following 25 words can be used to recover access to your wallet. Please write them down and store them somewhere safe and secure. Please do not store them in your email or on file storage services outside of your immediate control."+color_white+"\n")
   774  	fmt.Fprintf(os.Stderr, color_red+"%s"+color_white+"\n", seed)
   775  
   776  }
   777  
   778  // display spend key
   779  // viewable wallet do not have spend secret key
   780  // TODO wee need to give user a warning if we are printing secret
   781  func display_spend_key(l *readline.Instance, wallet *walletapi.Wallet) {
   782  
   783  	keys := wallet.Get_Keys()
   784  	if !account.ViewOnly {
   785  		fmt.Fprintf(os.Stderr, "spend key secret : "+color_red+"%s"+color_white+"\n", keys.Spendkey_Secret)
   786  	}
   787  	fmt.Fprintf(os.Stderr, "spend key public : %s\n", keys.Spendkey_Public)
   788  }
   789  
   790  //display view key
   791  func display_view_key(l *readline.Instance, wallet *walletapi.Wallet) {
   792  
   793  	keys := wallet.Get_Keys()
   794  	fmt.Fprintf(os.Stderr, "view key secret : "+color_yellow+"%s"+color_white+"\n", keys.Viewkey_Secret)
   795  	fmt.Fprintf(os.Stderr, "view key public : %s\n", keys.Viewkey_Public)
   796  
   797  }
   798  
   799  // display wallet view only Keys
   800  // this will create a watchable view only mode
   801  func display_viewwallet_key(l *readline.Instance, wallet *walletapi.Wallet) {
   802  
   803  	io.WriteString(l.Stderr(), fmt.Sprintf("This Key can used to create a watch only wallet. This wallet can only see incoming funds but cannot spend them.\nThe key is below.\n%s\n\n", wallet.GetViewWalletKey()))
   804  
   805  }
   806  
   807  // start a rescan from block 0
   808  func rescan_bc(wallet *walletapi.Wallet) {
   809  	if wallet.GetMode() { // trigger rescan we the wallet is online
   810  		wallet.Clean() // clean existing data from wallet
   811  		wallet.Rescan_From_Height(0)
   812  	}
   813  
   814  }
   815  
   816  // show the transfers to the user originating from this account
   817  func show_transfers(l *readline.Instance, wallet *walletapi.Wallet, limit uint64) {
   818  	available := true
   819  	in := true
   820  	out := true
   821  	pool := true    // this is not processed still TODO list
   822  	failed := false // this is not processed still TODO list
   823  	min_height := uint64(0)
   824  	max_height := uint64(0)
   825  
   826  	line := ""
   827  	line_parts := strings.Fields(line)
   828  	if len(line_parts) >= 2 {
   829  		switch strings.ToLower(line_parts[1]) {
   830  		case "available":
   831  			available = true
   832  			in = false
   833  			out = false
   834  			pool = false
   835  			failed = false
   836  		case "in":
   837  			available = true
   838  			in = true
   839  			out = false
   840  			pool = false
   841  			failed = false
   842  		case "out":
   843  			available = false
   844  			in = false
   845  			out = true
   846  			pool = false
   847  			failed = false
   848  		case "pool":
   849  			available = false
   850  			in = false
   851  			out = false
   852  			pool = true
   853  			failed = false
   854  		case "failed":
   855  			available = false
   856  			in = false
   857  			out = false
   858  			pool = false
   859  			failed = true
   860  
   861  		}
   862  	}
   863  
   864  	if len(line_parts) >= 3 { // user supplied min height
   865  		s, err := strconv.ParseUint(line_parts[2], 10, 64)
   866  		if err != nil {
   867  			globals.Logger.Warnf("Error parsing minimum height")
   868  			return
   869  		}
   870  		min_height = s
   871  	}
   872  
   873  	if len(line_parts) >= 4 { // user supplied max height
   874  		s, err := strconv.ParseUint(line_parts[2], 10, 64)
   875  		if err != nil {
   876  			globals.Logger.Warnf("Error parsing maximum height")
   877  			return
   878  		}
   879  		max_height = s
   880  	}
   881  
   882  	// request payments without payment id
   883  	transfers := wallet.Show_Transfers(available, in, out, pool, failed, false, min_height, max_height) // receives sorted list of transfers
   884  
   885  	if len(transfers) == 0 {
   886  		globals.Logger.Warnf("No transfers available")
   887  		return
   888  	}
   889  	// we need to paginate on say 20 transactions
   890  
   891  	paging := 20
   892  
   893  	/*if limit != 0 && uint64(len(transfers)) > limit {
   894  	   transfers = transfers[uint64(len(transfers))-limit:]
   895  	}*/
   896  	for i := range transfers {
   897  
   898  		switch transfers[i].Status {
   899  		case 0:
   900  
   901  			if len(transfers[i].PaymentID) == 0 {
   902  				io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO"+color_white+"\n",transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney12(transfers[i].Amount)))
   903  			} else {
   904  				payment_id := fmt.Sprintf("%x", transfers[i].PaymentID)
   905  				io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO"+color_white+" PAYMENT ID:%s\n",transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney12(transfers[i].Amount), payment_id))
   906  			}
   907  
   908  		case 1:
   909  			io.WriteString(l.Stderr(), fmt.Sprintf(color_magenta+"%s Height %d TopoHeight %d transaction %s spent %s DERO"+color_white+"\n",transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney12(transfers[i].Amount)))
   910  		case 2:
   911  			fallthrough
   912  		default:
   913  			globals.Logger.Warnf("Transaction status unknown TXID %s status %d", transfers[i].TXID, transfers[i].Status)
   914  
   915  		}
   916  
   917  		if i != 0 && i%paging == 0 && (i+1) < len(transfers) { // ask user whether he want to see more till he quits
   918  			if !ConfirmYesNoDefaultNo(l, "Want to see more history (y/N)?") {
   919  				break // break loop
   920  			}
   921  
   922  		}
   923  
   924  	}
   925  }