2022-01-26 10:05:01 +00:00

1019 lines
32 KiB
Go

// 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 "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
// 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 "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) >= 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
}
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("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("walletviewkey"),
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[1mget_tx_key\033[0m\tDisplay tx proof to prove receiver for specific transaction\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 <address> <amount>\n")
io.WriteString(w, "\t\033[1mtransfer_all\033[0m\tTransfer everything to another address\n")
io.WriteString(w, "\t\033[1mflush\033[0m\tFlush local wallet pool (for testing purposes)\n")
io.WriteString(w, "\t\033[1mversion\033[0m\t\tShow version\n")
io.WriteString(w, "\t\033[1mbye\033[0m\t\tQuit wallet\n")
io.WriteString(w, "\t\033[1mexit\033[0m\t\tQuit wallet\n")
io.WriteString(w, "\t\033[1mquit\033[0m\t\tQuit wallet\n")
}
// display seed to the user in his preferred language
func display_seed(l *readline.Instance, wallet *walletapi.Wallet_Disk) {
seed := wallet.GetSeed()
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")
fmt.Fprintf(os.Stderr, color_red+"%s"+color_white+"\n", seed)
}
// display spend key
// viewable wallet do not have spend secret key
// TODO wee need to give user a warning if we are printing secret
func display_spend_key(l *readline.Instance, wallet *walletapi.Wallet_Disk) {
keys := wallet.Get_Keys()
h := "0000000000000000000000000000000000000000000000" + keys.Secret.Text(16)
fmt.Fprintf(os.Stderr, "secret key: "+color_red+"%s"+color_white+"\n", h[len(h)-64:])
fmt.Fprintf(os.Stderr, "public key: %s\n", keys.Public.StringHex())
}
// start a rescan from block 0
func rescan_bc(wallet *walletapi.Wallet_Disk) {
if wallet.GetMode() { // trigger rescan we the wallet is online
wallet.Clean() // clean existing data from wallet
//wallet.Rescan_From_Height(0)
}
}
func valid_registration_or_display_error(l *readline.Instance, wallet *walletapi.Wallet_Disk) bool {
if !wallet.IsRegistered() {
logger.Error(fmt.Errorf("Your account is not registered.Please register."), "")
}
return true
}
// show the transfers to the user originating from this account
func show_transfers(l *readline.Instance, wallet *walletapi.Wallet_Disk, scid crypto.Hash, limit uint64) {
if wallet.GetMode() && walletapi.IsDaemonOnline() { // if wallet is in offline mode , we cannot do anything
if err := wallet.Sync_Wallet_Memory_With_Daemon_internal(scid); err != nil {
logger.Error(err, "Error syncing wallet", "scid", scid.String())
return
}
}
in := true
out := true
coinbase := true
min_height := uint64(0)
max_height := uint64(0)
line := ""
line_parts := strings.Fields(line)
if len(line_parts) >= 2 {
switch strings.ToLower(line_parts[1]) {
case "coinbase":
out = false
in = false
case "in":
coinbase = false
in = true
out = false
case "out":
coinbase = false
in = false
out = true
}
}
if len(line_parts) >= 3 { // user supplied min height
s, err := strconv.ParseUint(line_parts[2], 10, 64)
if err != nil {
logger.Error(err, "Error parsing minimum height")
return
}
min_height = s
}
if len(line_parts) >= 4 { // user supplied max height
s, err := strconv.ParseUint(line_parts[2], 10, 64)
if err != nil {
logger.Error(err, "Error parsing maximum height")
return
}
max_height = s
}
// request payments without payment id
transfers := wallet.Show_Transfers(scid, coinbase, in, out, min_height, max_height, "", "", 0, 0) // receives sorted list of transfers
if len(transfers) == 0 {
logger.Error(nil, "No transfers available")
return
}
// we need to paginate on say 20 transactions
paging := 20
//if limit != 0 && uint64(len(transfers)) > limit {
// transfers = transfers[uint64(len(transfers))-limit:]
//}
for i := len(transfers) - 1; i >= 0; i-- {
switch transfers[i].Status {
case 0:
if transfers[i].Coinbase {
io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d Coinbase (miner reward) received %s DERO"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, globals.FormatMoney(transfers[i].Amount)))
} else {
args, err := transfers[i].ProcessPayload()
if err != nil {
io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO Proof: %s"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Proof))
io.WriteString(l.Stderr(), fmt.Sprintf("Full Entry %+v\n", transfers[i])) // dump entire entry for debugging purposes
} else if len(args) == 0 { // no rpc
io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO Proof: %s NO RPC CALL"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Proof))
} else { // yes, its rpc
io.WriteString(l.Stderr(), fmt.Sprintf(color_green+"%s Height %d TopoHeight %d transaction %s received %s DERO Proof: %s RPC CALL arguments %s "+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Proof, args))
}
}
case 1:
args, err := transfers[i].ProcessPayload()
if err != nil {
io.WriteString(l.Stderr(), fmt.Sprintf(color_yellow+"%s Height %d TopoHeight %d transaction %s spent %s DERO Destination: %s Proof: %s\n"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Destination, transfers[i].Proof))
io.WriteString(l.Stderr(), fmt.Sprintf("Err decoding entry %s\nFull Entry %+v\n", err, transfers[i])) // dump entire entry for debugging purposes
} else if len(args) == 0 { // no rpc
io.WriteString(l.Stderr(), fmt.Sprintf(color_yellow+"%s Height %d TopoHeight %d transaction %s spent %s DERO Destination: %s Proof: %s NO RPC CALL"+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Destination, transfers[i].Proof))
} else { // yes, its rpc
io.WriteString(l.Stderr(), fmt.Sprintf(color_yellow+"%s Height %d TopoHeight %d transaction %s spent %s DERO Destination: %s Proof: %s RPC CALL arguments %s "+color_white+"\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), transfers[i].Destination, transfers[i].Proof, args))
}
case 2:
fallthrough
default:
logger.Error(nil, "Transaction status unknown TXID %s status %d", transfers[i].TXID, transfers[i].Status)
}
j := len(transfers) - i
if j != 0 && j%paging == 0 && (j+1) < len(transfers) { // ask user whether he want to see more till he quits
if !ConfirmYesNoDefaultNo(l, "Want to see more history (y/N)?") {
break // break loop
}
}
}
}