// Copyright 2017-2021 DERO Project. All rights reserved. // Use of this source code in any form is governed by RESEARCH license. // license can be found in the LICENSE file. // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 // // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package main import "os" import "io" import "fmt" import "bytes" import "time" //import "io/ioutil" //import "path/filepath" import "strings" import "unicode" import "strconv" import "encoding/hex" import "github.com/chzyer/readline" import "github.com/deroproject/derohe/rpc" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/globals" import "github.com/deroproject/derohe/walletapi" import "github.com/deroproject/derohe/cryptography/crypto" var account walletapi.Account func isASCII(s string) bool { for _, c := range s { if c > unicode.MaxASCII { return false } } return true } // handle all commands while in prompt mode func handle_prompt_command(l *readline.Instance, line string) { var err error line = strings.TrimSpace(line) line_parts := strings.Fields(line) if len(line_parts) < 1 { // if no command return return } _ = err command := "" if len(line_parts) >= 1 { command = strings.ToLower(line_parts[0]) } // handled closed wallet commands switch command { case "address", "rescan_bc", "seed", "set", "password", "get_tx_key", "i8", "payment_id": fallthrough case "spendkey", "transfer", "close": fallthrough case "transfer_all", "sweep_all", "show_transfers", "balance", "status": if wallet == nil { logger.Error(err, "No wallet available") return } } switch command { case "help": usage(l.Stderr()) case "address": // give user his account address fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+"\n", wallet.GetAddress()) case "status": // show syncronisation status fmt.Fprintf(l.Stderr(), "Wallet Version : %s\n", config.Version.String()) fmt.Fprintf(l.Stderr(), "Wallet Height : %d\t Daemon Height %d \n", wallet.Get_Height(), wallet.Get_Daemon_Height()) fallthrough case "balance": // give user his balance balance_unlocked, locked_balance := wallet.Get_Balance_Rescan() fmt.Fprintf(l.Stderr(), "DERO Balance : "+color_green+"%s"+color_white+"\n", globals.FormatMoney(locked_balance+balance_unlocked)) line_parts := line_parts[1:] // remove first part switch len(line_parts) { case 0: //logger.Error(err,"not implemented") break case 1: // scid balance scid := crypto.HashHexToHash(line_parts[0]) //logger.Info("scid1 %s line_parts %+v", scid, line_parts) balance, _, err := wallet.GetDecryptedBalanceAtTopoHeight(scid, -1, wallet.GetAddress().String()) //logger.Info("scid %s", scid) if err != nil { logger.Error(err, "error during Sc balance", "scid", scid.String()) } else { fmt.Fprintf(l.Stderr(), "SCID %s Balance : "+color_green+"%s"+color_white+"\n\n", line_parts[0], globals.FormatMoney(balance)) } case 2: // scid balance at topoheight logger.Error(err, "not implemented") break } case "rescan_bc", "rescan_spent": // rescan from 0 if offline_mode { logger.Error(err, "Offline wallet rescanning NOT implemented") } else { rescan_bc(wallet) } case "seed": // give user his seed, if password is valid if !ValidateCurrentPassword(l, wallet) { logger.Error(err, "Invalid password") PressAnyKey(l, wallet) break } display_seed(l, wallet) // seed should be given only to authenticated users case "spendkey": // give user his spend key display_spend_key(l, wallet) case "filesign": // sign a file contents if !ValidateCurrentPassword(l, wallet) { logger.Error(err, "Invalid password") PressAnyKey(l, wallet) break } filename, err := ReadString(l, "Enter file to sign", "") if err != nil { logger.Error(err, "Cannot read input file name") } outputfile := filename + ".sign" if filedata, err := os.ReadFile(filename); err != nil { logger.Error(err, "Cannot read input file") } else if err := os.WriteFile(outputfile, wallet.SignData(filedata), 0600); err != nil { logger.Error(err, "Cannot write output file", "file", outputfile) } else { logger.Info("successfully signed file. please check", "file", outputfile) } case "fileverify": // verify a file contents filename, err := ReadString(l, "Enter file to verify signature", "") if err != nil { logger.Error(err, "Cannot read input file name") } outputfile := strings.TrimSuffix(filename, ".sign") if filedata, err := os.ReadFile(filename); err != nil { logger.Error(err, "Cannot read input file") } else if signer, message, err := wallet.CheckSignature(filedata); err != nil { logger.Error(err, "Signature verify failed", "file", filename) } else { logger.Info("Signed by", "address", signer.String()) if isASCII(string(message)) { // do not spew garbage logger.Info("", "message", string(message)) } if os.WriteFile(outputfile, message, 0600); err != nil { logger.Error(err, "Cannot write output file", "file", outputfile) } logger.Info("successfully wrote message to file. please check", "file", outputfile) } case "password": // change wallet password if ConfirmYesNoDefaultNo(l, "Change wallet password (y/N)") && ValidateCurrentPassword(l, wallet) { new_password := ReadConfirmedPassword(l, "Enter new password", "Confirm password") err = wallet.Set_Encrypted_Wallet_Password(new_password) if err == nil { logger.Info("Wallet password successfully changed") } else { logger.Error(err, "Wallet password could not be changed") } } case "get_tx_key": if !valid_registration_or_display_error(l, wallet) { break } if len(line_parts) == 2 && len(line_parts[1]) == 64 { _, err := hex.DecodeString(line_parts[1]) if err != nil { logger.Error(err, "Error parsing txhash") break } key := wallet.GetTXKey(line_parts[1]) if key != "" { logger.Info("TX Proof key \"%s\"", key) } else { logger.Error(err, "TX not found in database") } } else { logger.Info("get_tx_key needs transaction hash as input parameter") logger.Info("eg. get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7") } case "sweep_all", "transfer_all": // transfer everything //Transfer_Everything(l) case "show_transfers": switch len(line_parts) { case 1: var zeroscid crypto.Hash show_transfers(l, wallet, zeroscid, 100) break case 2: // scid balance scid := crypto.HashHexToHash(line_parts[1]) show_transfers(l, wallet, scid, 100) default: logger.Error(err, "unknown parameters or not implemented") break } case "set": // set/display different settings handle_set_command(l, line) case "close": // close the account if !ValidateCurrentPassword(l, wallet) { logger.Error(err, "Invalid password") break } wallet.Close_Encrypted_Wallet() // overwrite previous instance case "menu": // enable menu mode menu_mode = true logger.Info("Menu mode enabled") case "i8", "integrated_address": // user wants a random integrated address 8 bytes a := wallet.GetRandomIAddress8() fmt.Fprintf(l.Stderr(), "Wallet integrated address : "+color_green+"%s"+color_white+"\n", a.String()) fmt.Fprintf(l.Stderr(), "Embedded Arguments : "+color_green+"%s"+color_white+"\n", a.Arguments) case "version": logger.Info("", "Version", config.Version.String()) case "burn": line_parts := line_parts[1:] // remove first part if len(line_parts) < 2 { logger.Error(err, "burn needs destination address and amount as input parameter") break } addr := line_parts[0] send_amount := uint64(1) burn_amount, err := globals.ParseAmount(line_parts[1]) if err != nil { logger.Error(err, "Error Parsing burn amount", "raw", line_parts[1]) return } if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") { //uid, err := wallet.PoolTransferWithBurn(addr, send_amount, burn_amount, data, rpc.Arguments{}) tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{Amount: send_amount, Burn: burn_amount, Destination: addr}}, 0, false, rpc.Arguments{}, 0, false) // empty SCDATA if err != nil { logger.Error(err, "Error while building Transaction") break } if err = wallet.SendTransaction(tx); err != nil { logger.Error(err, "Error while dispatching Transaction") return } logger.Info("Dispatched tx", "txid", tx.GetHash().String()) //fmt.Printf("queued tx err %s\n", err) //build_relay_transaction(l, uid, err, offline_tx, amount_list) } case "transfer": // parse the address, amount pair /* line_parts := line_parts[1:] // remove first part addr_list := []address.Address{} amount_list := []uint64{} payment_id := "" for i := 0; i < len(line_parts); { globals.Logger.Debugf("len %d %+v", len(line_parts), line_parts) if len(line_parts) >= 2 { // parse address amount pair addr, err := globals.ParseValidateAddress(line_parts[0]) if err != nil { logger.Error(err,"Error Parsing \"%s\" err %s", line_parts[0], err) return } amount, err := globals.ParseAmount(line_parts[1]) if err != nil { logger.Error(err,"Error Parsing \"%s\" err %s", line_parts[1], err) return } line_parts = line_parts[2:] // remove parsed addr_list = append(addr_list, *addr) amount_list = append(amount_list, amount) continue } if len(line_parts) == 1 { // parse payment_id if len(line_parts[0]) == 64 || len(line_parts[0]) == 16 { _, err := hex.DecodeString(line_parts[0]) if err != nil { logger.Error(err,"Error parsing payment ID, it should be in hex 16 or 64 chars") return } payment_id = line_parts[0] line_parts = line_parts[1:] } else { logger.Error(err,"Invalid payment ID \"%s\"", line_parts[0]) return } } } // check if everything is okay, if yes build the transaction if len(addr_list) == 0 { logger.Error(err,"Destination address not provided") return } payment_id_integrated := false for i := range addr_list { if addr_list[i].IsIntegratedAddress() { payment_id_integrated = true logger.Info("Payment ID is integreted in address ID:%x", addr_list[i].PaymentID) } } offline := false tx, inputs, input_sum, change, err := wallet.Transfer(addr_list, amount_list, 0, payment_id, 0, 0) build_relay_transaction(l, tx, inputs, input_sum, change, err, offline, amount_list) */ case "q", "bye", "exit", "quit": globals.Exit_In_Progress = true if wallet != nil { wallet.Close_Encrypted_Wallet() // overwrite previous instance } case "flush": // flush wallet pool logger.Error(err, "No such command") case "": // blank enter key just loop default: //fmt.Fprintf(l.Stderr(), "you said: %s", strconv.Quote(line)) logger.Error(err, "No such command") } } // handle all commands while in prompt mode func handle_set_command(l *readline.Instance, line string) { //var err error line = strings.TrimSpace(line) line_parts := strings.Fields(line) if len(line_parts) < 1 { // if no command return return } command := "" if len(line_parts) >= 2 { command = strings.ToLower(line_parts[1]) } help := false switch command { case "help": case "ringsize": if len(line_parts) != 3 { logger.Info("Wrong number of arguments, see help eg", "") help = true break } s, err := strconv.ParseUint(line_parts[2], 10, 64) if err != nil { logger.Error(err, "Error parsing ringsize") return } wallet.SetRingSize(int(s)) logger.Info("New Ring size", "ringsize", wallet.GetRingSize()) case "priority": if len(line_parts) != 3 { logger.Info("Wrong number of arguments, see help eg") help = true break } s, err := strconv.ParseFloat(line_parts[2], 64) if err != nil { logger.Error(err, "Error parsing priority") return } wallet.SetFeeMultiplier(float32(s)) logger.Info("Transaction", "priority", wallet.GetFeeMultiplier()) case "seed": // seed only has 1 setting, lanuage so do it now language := choose_seed_language(l) logger.Info("Setting seed language", "language", wallet.SetSeedLanguage(language)) default: help = true } if help == true || len(line_parts) == 1 { // user type plain set command, give out all settings and help fmt.Fprintf(l.Stderr(), color_extra_white+"Current settings"+color_extra_white+"\n") 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()) fmt.Fprintf(l.Stderr(), color_normal+"Ringsize: "+color_extra_white+"%d\t"+color_normal+"eg. "+color_extra_white+"set ringsize 16\n"+color_normal, wallet.GetRingSize()) 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()) fmt.Fprintf(l.Stderr(), "\t\tMinimum priority is 1.00. High priority = high fees\n") } } // read an address with all goodies such as color encoding and other things in prompt func ReadAddress(l *readline.Instance, wallet *walletapi.Wallet_Disk) (a *rpc.Address, err error) { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.EnableMask = false prompt_mutex.Lock() defer prompt_mutex.Unlock() var linestr string setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { error_message := "" color := color_green if len(line) >= 1 { _, err := globals.ParseValidateAddress(string(line)) if err != nil { if linestr, err = wallet.NameToAddress(string(strings.TrimSpace(string(line)))); err != nil { error_message = " " //err.Error() } else { } } } if error_message != "" { color = color_red // Should we display the error message here?? l.SetPrompt(fmt.Sprintf("%sEnter Destination Address: ", color)) } else { l.SetPrompt(fmt.Sprintf("%sEnter Destination Address: ", color)) } l.Refresh() return nil, 0, false }) line, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { return } if linestr == "" { a, err = globals.ParseValidateAddress(string(line)) } else { a, err = globals.ParseValidateAddress(string(linestr)) } l.SetPrompt(prompt) l.Refresh() return } // read an address with all goodies such as color encoding and other things in prompt func ReadSCID(l *readline.Instance) (a crypto.Hash, err error) { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.EnableMask = false prompt_mutex.Lock() defer prompt_mutex.Unlock() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { error_message := "" color := color_green if len(line) >= 1 { err := a.UnmarshalText([]byte(string(line))) if err != nil { error_message = " " //err.Error() } } if error_message != "" { color = color_red // Should we display the error message here?? l.SetPrompt(fmt.Sprintf("%sEnter SCID: ", color)) } else { l.SetPrompt(fmt.Sprintf("%sEnter SCID: ", color)) } l.Refresh() return nil, 0, false }) line, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { return } err = a.UnmarshalText([]byte(string(line))) l.SetPrompt(prompt) l.Refresh() return } func ReadFloat64(l *readline.Instance, cprompt string, default_value float64) (a float64, err error) { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.EnableMask = false prompt_mutex.Lock() defer prompt_mutex.Unlock() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { error_message := "" color := color_green if len(line) >= 1 { _, err := strconv.ParseFloat(string(line), 64) if err != nil { error_message = " " //err.Error() } } if error_message != "" { color = color_red // Should we display the error message here?? l.SetPrompt(fmt.Sprintf("%sEnter %s (default %f): ", color, cprompt, default_value)) } else { l.SetPrompt(fmt.Sprintf("%sEnter %s (default %f): ", color, cprompt, default_value)) } l.Refresh() return nil, 0, false }) line, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { return } a, err = strconv.ParseFloat(string(line), 64) l.SetPrompt(cprompt) l.Refresh() return } func ReadUint64(l *readline.Instance, cprompt string, default_value uint64) (a uint64, err error) { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.EnableMask = false prompt_mutex.Lock() defer prompt_mutex.Unlock() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { error_message := "" color := color_green if len(line) == 0 { line = []rune(fmt.Sprintf("%d", default_value)) } if len(line) >= 1 { _, err := strconv.ParseUint(string(line), 0, 64) if err != nil { error_message = " " //err.Error() } } if error_message != "" { color = color_red // Should we display the error message here?? l.SetPrompt(fmt.Sprintf("%sEnter %s (default %d): ", color, cprompt, default_value)) } else { l.SetPrompt(fmt.Sprintf("%sEnter %s (default %d): ", color, cprompt, default_value)) } l.Refresh() return nil, 0, false }) line, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { return } if len(line) == 0 { line = []byte(fmt.Sprintf("%d", default_value)) } a, err = strconv.ParseUint(string(line), 0, 64) l.SetPrompt(cprompt) l.Refresh() return } func ReadInt64(l *readline.Instance, cprompt string, default_value int64) (a int64, err error) { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.EnableMask = false prompt_mutex.Lock() defer prompt_mutex.Unlock() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { error_message := "" color := color_green if len(line) >= 1 { _, err := strconv.ParseInt(string(line), 0, 64) if err != nil { error_message = " " //err.Error() } } if error_message != "" { color = color_red // Should we display the error message here?? l.SetPrompt(fmt.Sprintf("%sEnter %s (default %d): ", color, cprompt, default_value)) } else { l.SetPrompt(fmt.Sprintf("%sEnter %s (default %d): ", color, cprompt, default_value)) } l.Refresh() return nil, 0, false }) line, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { return } a, err = strconv.ParseInt(string(line), 0, 64) l.SetPrompt(cprompt) l.Refresh() return } func ReadString(l *readline.Instance, cprompt string, default_value string) (a string, err error) { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.EnableMask = false prompt_mutex.Lock() defer prompt_mutex.Unlock() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { error_message := "" color := color_green if len(line) < 1 { error_message = " " //err.Error() } if error_message != "" { color = color_red // Should we display the error message here?? l.SetPrompt(fmt.Sprintf("%sEnter %s (default '%s'): ", color, cprompt, default_value)) } else { l.SetPrompt(fmt.Sprintf("%sEnter %s (default '%s'): ", color, cprompt, default_value)) } l.Refresh() return nil, 0, false }) line, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { return } a = string(line) l.SetPrompt(cprompt) l.Refresh() return } // confirms whether the user wants to confirm yes func ConfirmYesNoDefaultYes(l *readline.Instance, prompt_temporary string) bool { prompt_mutex.Lock() defer prompt_mutex.Unlock() l.SetPrompt(prompt_temporary) line, err := l.Readline() if err == readline.ErrInterrupt { if len(line) == 0 { logger.Info("Ctrl-C received, Exiting") os.Exit(0) } } else if err == io.EOF { os.Exit(0) } l.SetPrompt(prompt) l.Refresh() if strings.TrimSpace(line) == "n" || strings.TrimSpace(line) == "N" { return false } return true } // confirms whether the user wants to confirm NO func ConfirmYesNoDefaultNo(l *readline.Instance, prompt_temporary string) bool { prompt_mutex.Lock() defer prompt_mutex.Unlock() l.SetPrompt(prompt_temporary) line, err := l.Readline() if err == readline.ErrInterrupt { if len(line) == 0 { logger.Info("Ctrl-C received, Exiting") os.Exit(0) } } else if err == io.EOF { os.Exit(0) } l.SetPrompt(prompt) if strings.TrimSpace(line) == "y" || strings.TrimSpace(line) == "Y" { return true } return false } // confirms whether user knows the current password for the wallet // this is triggerred while transferring amount, changing settings and so on func ValidateCurrentPassword(l *readline.Instance, wallet *walletapi.Wallet_Disk) bool { prompt_mutex.Lock() defer prompt_mutex.Unlock() // if user requested wallet to be open/unlocked, keep it open if globals.Arguments["--unlocked"].(bool) == true { return true } setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { l.SetPrompt(fmt.Sprintf("Enter current wallet password(%v): ", len(line))) l.Refresh() return nil, 0, false }) //pswd, err := l.ReadPassword("please enter your password: ") pswd, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { return false } // something was read, check whether it's the password setup in the wallet return wallet.Check_Password(string(pswd)) } // reads a password to open the wallet func ReadPassword(l *readline.Instance, filename string) string { prompt_mutex.Lock() defer prompt_mutex.Unlock() try_again: setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { l.SetPrompt(fmt.Sprintf("Enter wallet password for %s (%v): ", filename, len(line))) l.Refresh() return nil, 0, false }) //pswd, err := l.ReadPassword("please enter your password: ") pswd, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { goto try_again } // something was read, check whether it's the password setup in the wallet return string(pswd) } func ReadConfirmedPassword(l *readline.Instance, first_prompt string, second_prompt string) (password string) { prompt_mutex.Lock() defer prompt_mutex.Unlock() for { setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { l.SetPrompt(fmt.Sprintf("%s(%v): ", first_prompt, len(line))) l.Refresh() return nil, 0, false }) password_bytes, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { //return continue } setPasswordCfg = l.GenPasswordConfig() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { l.SetPrompt(fmt.Sprintf("%s(%v): ", second_prompt, len(line))) l.Refresh() return nil, 0, false }) confirmed_bytes, err := l.ReadPasswordWithConfig(setPasswordCfg) if err != nil { //return continue } if bytes.Equal(password_bytes, confirmed_bytes) { password = string(password_bytes) err = nil return } logger.Error(fmt.Errorf("Passwords mismatch.Retrying."), "") } } // confirms user to press a key // this is triggerred while transferring amount, changing settings and so on func PressAnyKey(l *readline.Instance, wallet *walletapi.Wallet_Disk) { prompt_mutex.Lock() defer prompt_mutex.Unlock() setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { l.SetPrompt(fmt.Sprintf("Press ENTER key to continue...")) l.Refresh() return nil, 0, false }) // any error or any key is the same l.ReadPasswordWithConfig(setPasswordCfg) return } // this completer is used to complete the commands at the prompt // BUG, this needs to be disabled in menu mode var completer = readline.NewPrefixCompleter( readline.PcItem("help"), readline.PcItem("address"), readline.PcItem("balance"), readline.PcItem("integrated_address"), readline.PcItem("get_tx_key"), readline.PcItem("filesign"), readline.PcItem("fileverify"), readline.PcItem("menu"), readline.PcItem("rescan_bc"), readline.PcItem("payment_id"), readline.PcItem("print_height"), readline.PcItem("seed"), readline.PcItem("set", readline.PcItem("mixin"), readline.PcItem("seed"), readline.PcItem("priority"), ), readline.PcItem("show_transfers"), readline.PcItem("spendkey"), readline.PcItem("status"), readline.PcItem("version"), readline.PcItem("transfer"), readline.PcItem("transfer_all"), readline.PcItem("bye"), readline.PcItem("exit"), readline.PcItem("quit"), ) // help command screen func usage(w io.Writer) { io.WriteString(w, "commands:\n") io.WriteString(w, "\t\033[1mhelp\033[0m\t\tthis help\n") io.WriteString(w, "\t\033[1maddress\033[0m\t\tDisplay user address\n") io.WriteString(w, "\t\033[1mbalance\033[0m\t\tDisplay user balance\n") io.WriteString(w, "\t\033[1mintegrated_address\033[0m\tDisplay random integrated address (with encrypted payment ID)\n") io.WriteString(w, "\t\033[1mmenu\033[0m\t\tEnable menu mode\n") io.WriteString(w, "\t\033[1mrescan_bc\033[0m\tRescan blockchain to re-obtain transaction history \n") io.WriteString(w, "\t\033[1mpassword\033[0m\tChange wallet password\n") io.WriteString(w, "\t\033[1mpayment_id\033[0m\tPrint random Payment ID (for encrypted version see integrated_address)\n") io.WriteString(w, "\t\033[1mseed\033[0m\t\tDisplay seed\n") io.WriteString(w, "\t\033[1mshow_transfers\033[0m\tShow all transactions to/from current wallet\n") io.WriteString(w, "\t\033[1mset\033[0m\t\tSet/get various settings\n") io.WriteString(w, "\t\033[1mstatus\033[0m\t\tShow general information and balance\n") io.WriteString(w, "\t\033[1mspendkey\033[0m\tView secret key\n") io.WriteString(w, "\t\033[1mtransfer\033[0m\tTransfer/Send DERO to another address\n") io.WriteString(w, "\t\t\tEg. transfer