902 lines
28 KiB
Go
902 lines
28 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/config"
|
|
import "github.com/deroproject/derohe/crypto"
|
|
import "github.com/deroproject/derohe/globals"
|
|
import "github.com/deroproject/derohe/address"
|
|
import "github.com/deroproject/derohe/walletapi"
|
|
|
|
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 {
|
|
globals.Logger.Warnf("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(), "Balance : "+color_green+"%s"+color_white+"\n\n", globals.FormatMoney(locked_balance+balance_unlocked))
|
|
|
|
case "rescan_bc", "rescan_spent": // rescan from 0
|
|
if offline_mode {
|
|
globals.Logger.Warnf("Offline wallet rescanning NOT implemented")
|
|
} else {
|
|
rescan_bc(wallet)
|
|
}
|
|
|
|
case "seed": // give user his seed, if password is valid
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
globals.Logger.Warnf("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 {
|
|
globals.Logger.Infof("Wallet password successfully changed")
|
|
} else {
|
|
globals.Logger.Warnf("Wallet password could not be changed err %s", err)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
globals.Logger.Warnf("Error parsing txhash")
|
|
break
|
|
}
|
|
key := wallet.GetTXKey(crypto.HexToHash(line_parts[1]))
|
|
if key != "" {
|
|
globals.Logger.Infof("TX Proof key \"%s\"", key)
|
|
} else {
|
|
globals.Logger.Warnf("TX not found in database")
|
|
}
|
|
} else {
|
|
globals.Logger.Warnf("get_tx_key needs transaction hash as input parameter")
|
|
globals.Logger.Warnf("eg. get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7")
|
|
}
|
|
case "sweep_all", "transfer_all": // transfer everything
|
|
Transfer_Everything(l)
|
|
|
|
case "show_transfers":
|
|
show_transfers(l, wallet, 100)
|
|
case "set": // set/display different settings
|
|
handle_set_command(l, line)
|
|
case "close": // close the account
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
globals.Logger.Warnf("Invalid password")
|
|
break
|
|
}
|
|
wallet.Close_Encrypted_Wallet() // overwrite previous instance
|
|
|
|
case "menu": // enable menu mode
|
|
menu_mode = true
|
|
globals.Logger.Infof("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 payment ID : "+color_green+"%x"+color_white+"\n", a.PaymentID)
|
|
|
|
case "version":
|
|
globals.Logger.Infof("Version %s\n", config.Version.String())
|
|
|
|
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 {
|
|
globals.Logger.Warnf("Error Parsing \"%s\" err %s", line_parts[0], err)
|
|
return
|
|
}
|
|
amount, err := globals.ParseAmount(line_parts[1])
|
|
if err != nil {
|
|
globals.Logger.Warnf("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 {
|
|
globals.Logger.Warnf("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 {
|
|
globals.Logger.Warnf("Invalid payment ID \"%s\"", line_parts[0])
|
|
return
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// check if everything is okay, if yes build the transaction
|
|
if len(addr_list) == 0 {
|
|
globals.Logger.Warnf("Destination address not provided")
|
|
return
|
|
}
|
|
|
|
payment_id_integrated := false
|
|
for i := range addr_list {
|
|
if addr_list[i].IsIntegratedAddress() {
|
|
payment_id_integrated = true
|
|
globals.Logger.Infof("Payment ID is integreted in address ID:%x", addr_list[i].PaymentID)
|
|
}
|
|
|
|
}
|
|
|
|
// if user provided an integrated address donot ask him payment id
|
|
// otherwise confirm whether user wants to send without payment id
|
|
if payment_id_integrated == false && len(payment_id) == 0 {
|
|
payment_id_bytes, err := ReadPaymentID(l)
|
|
payment_id = hex.EncodeToString(payment_id_bytes)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Err :%s", err)
|
|
break
|
|
}
|
|
}
|
|
|
|
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 "": // blank enter key just loop
|
|
default:
|
|
//fmt.Fprintf(l.Stderr(), "you said: %s", strconv.Quote(line))
|
|
globals.Logger.Warnf("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 {
|
|
globals.Logger.Warnf("Wrong number of arguments, see help eg")
|
|
help = true
|
|
break
|
|
}
|
|
s, err := strconv.ParseUint(line_parts[2], 10, 64)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Error parsing ringsize")
|
|
return
|
|
}
|
|
wallet.SetRingSize(int(s))
|
|
globals.Logger.Infof("Ring size = %d", wallet.GetRingSize())
|
|
|
|
case "priority":
|
|
if len(line_parts) != 3 {
|
|
globals.Logger.Warnf("Wrong number of arguments, see help eg")
|
|
help = true
|
|
break
|
|
}
|
|
s, err := strconv.ParseFloat(line_parts[2], 64)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Error parsing priority")
|
|
return
|
|
}
|
|
wallet.SetFeeMultiplier(float32(s))
|
|
globals.Logger.Infof("Transaction priority = %.02f", wallet.GetFeeMultiplier())
|
|
|
|
case "seed": // seed only has 1 setting, lanuage so do it now
|
|
language := choose_seed_language(l)
|
|
globals.Logger.Infof("Setting seed language to \"%s\"", 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")
|
|
|
|
}
|
|
}
|
|
|
|
func Transfer_Everything(l *readline.Instance) {
|
|
/*
|
|
if wallet.Is_View_Only() {
|
|
fmt.Fprintf(l.Stderr(), color_yellow+"View Only wallet cannot transfer."+color_white)
|
|
}
|
|
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
globals.Logger.Warnf("Invalid password")
|
|
return
|
|
}
|
|
|
|
// a , amount_to_transfer, err := collect_transfer_info(l,wallet)
|
|
addr, err := ReadAddress(l)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Err :%s", err)
|
|
return
|
|
}
|
|
|
|
var payment_id []byte
|
|
// if user provided an integrated address donot ask him payment id
|
|
if !addr.IsIntegratedAddress() {
|
|
payment_id, err = ReadPaymentID(l)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Err :%s", err)
|
|
return
|
|
}
|
|
} else {
|
|
globals.Logger.Infof("Payment ID is integreted in address ID:%x", addr.PaymentID)
|
|
}
|
|
|
|
fees_per_kb := uint64(0) // fees must be calculated by walletapi
|
|
|
|
tx, inputs, input_sum, err := wallet.Transfer_Everything(*addr, hex.EncodeToString(payment_id), 0, fees_per_kb, 5)
|
|
|
|
_ = inputs
|
|
if err != nil {
|
|
globals.Logger.Warnf("Error while building Transaction err %s\n", err)
|
|
return
|
|
}
|
|
globals.Logger.Infof("%d Inputs Selected for %s DERO", len(inputs), globals.FormatMoney12(input_sum))
|
|
globals.Logger.Infof("fees %s DERO", globals.FormatMoneyPrecision(tx.RctSignature.Get_TX_Fee(), 12))
|
|
globals.Logger.Infof("TX Size %0.1f KiB (should be < 240 KiB)", float32(len(tx.Serialize()))/1024.0)
|
|
offline_tx := false
|
|
if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") {
|
|
|
|
if offline_tx { // if its an offline tx, dump it to a file
|
|
cur_dir, err := os.Getwd()
|
|
if err != nil {
|
|
globals.Logger.Warnf("Cannot obtain current directory to save tx")
|
|
return
|
|
}
|
|
filename := filepath.Join(cur_dir, tx.GetHash().String()+".tx")
|
|
err = ioutil.WriteFile(filename, []byte(hex.EncodeToString(tx.Serialize())), 0600)
|
|
if err == nil {
|
|
if err == nil {
|
|
globals.Logger.Infof("Transaction saved successfully. txid = %s", tx.GetHash())
|
|
globals.Logger.Infof("Saved to %s", filename)
|
|
} else {
|
|
globals.Logger.Warnf("Error saving tx to %s , err %s", filename, err)
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
err = wallet.SendTransaction(tx) // relay tx to daemon/network
|
|
if err == nil {
|
|
globals.Logger.Infof("Transaction sent successfully. txid = %s", tx.GetHash())
|
|
} else {
|
|
globals.Logger.Warnf("Transaction sending failed txid = %s, err %s", tx.GetHash(), err)
|
|
}
|
|
|
|
}
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
// read an address with all goodies such as color encoding and other things in prompt
|
|
func ReadAddress(l *readline.Instance) (a *address.Address, 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 := globals.ParseValidateAddress(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 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
|
|
}
|
|
a, err = globals.ParseValidateAddress(string(line))
|
|
l.SetPrompt(prompt)
|
|
l.Refresh()
|
|
return
|
|
}
|
|
|
|
/*
|
|
// read an payment with all goodies such as color encoding and other things in prompt
|
|
func ReadPaymentID(l *readline.Instance) (payment_id []byte, err error) {
|
|
setPasswordCfg := l.GenPasswordConfig()
|
|
setPasswordCfg.EnableMask = false
|
|
|
|
// ask user whether he want to enter a payment ID
|
|
|
|
if !ConfirmYesNoDefaultNo(l, "Provide Payment ID (y/N)") { // user doesnot want to provide payment it, skip
|
|
return
|
|
}
|
|
|
|
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 := hex.DecodeString(string(line))
|
|
if (len(line) == 16 || len(line) == 64) && err == nil {
|
|
error_message = ""
|
|
} else {
|
|
error_message = " " //err.Error()
|
|
}
|
|
}
|
|
|
|
if error_message != "" {
|
|
color = color_red // Should we display the error message here??
|
|
l.SetPrompt(fmt.Sprintf("%sEnter Payment ID (16/64 hex char): ", color))
|
|
} else {
|
|
l.SetPrompt(fmt.Sprintf("%sEnter Payment ID (16/64 hex char): ", color))
|
|
|
|
}
|
|
|
|
l.Refresh()
|
|
return nil, 0, false
|
|
})
|
|
|
|
line, err := l.ReadPasswordWithConfig(setPasswordCfg)
|
|
if err != nil {
|
|
return
|
|
}
|
|
payment_id, err = hex.DecodeString(string(line))
|
|
if err != nil {
|
|
return
|
|
}
|
|
l.SetPrompt(prompt)
|
|
l.Refresh()
|
|
|
|
if len(payment_id) == 8 || len(payment_id) == 32 {
|
|
return
|
|
}
|
|
|
|
err = fmt.Errorf("Invalid Payment ID")
|
|
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 {
|
|
globals.Logger.Infof("Ctrl-C received, Exiting\n")
|
|
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 {
|
|
globals.Logger.Infof("Ctrl-C received, Exiting\n")
|
|
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) 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
|
|
}
|
|
|
|
globals.Logger.Warnf("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) {
|
|
|
|
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
|
|
}
|
|
|
|
/*
|
|
// if we are in offline, scan default or user provided file
|
|
// this function will replay the blockchain data in offline mode
|
|
func trigger_offline_data_scan() {
|
|
filename := default_offline_datafile
|
|
|
|
if globals.Arguments["--offline_datafile"] != nil {
|
|
filename = globals.Arguments["--offline_datafile"].(string)
|
|
}
|
|
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Cannot read offline data file=\"%s\" err: %s ", filename, err)
|
|
return
|
|
}
|
|
w := bufio.NewReader(f)
|
|
gzipreader, err := gzip.NewReader(w)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Error while decompressing offline data file=\"%s\" err: %s ", filename, err)
|
|
return
|
|
}
|
|
defer gzipreader.Close()
|
|
io.Copy(pipe_writer, gzipreader)
|
|
}
|
|
*/
|
|
|
|
// 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[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) {
|
|
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) {
|
|
|
|
keys := wallet.Get_Keys()
|
|
fmt.Fprintf(os.Stderr, "secret key: "+color_red+"%s"+color_white+"\n", keys.Secret.Text(16))
|
|
|
|
fmt.Fprintf(os.Stderr, "public key: %s\n", keys.Public.StringHex())
|
|
}
|
|
|
|
// start a rescan from block 0
|
|
func rescan_bc(wallet *walletapi.Wallet) {
|
|
if wallet.GetMode() { // trigger rescan we the wallet is online
|
|
wallet.Clean() // clean existing data from wallet
|
|
//wallet.Rescan_From_Height(0)
|
|
}
|
|
|
|
}
|
|
|
|
func is_registered(wallet *walletapi.Wallet) bool {
|
|
if wallet.Get_Registration_TopoHeight() == -1 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func valid_registration_or_display_error(l *readline.Instance, wallet *walletapi.Wallet) bool {
|
|
if !is_registered(wallet) {
|
|
globals.Logger.Warnf("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, limit uint64) {
|
|
|
|
available := true
|
|
in := true
|
|
out := true
|
|
pool := true // this is not processed still TODO list
|
|
failed := false // this is not processed still TODO list
|
|
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 "available":
|
|
available = true
|
|
in = false
|
|
out = false
|
|
pool = false
|
|
failed = false
|
|
case "in":
|
|
available = true
|
|
in = true
|
|
out = false
|
|
pool = false
|
|
failed = false
|
|
case "out":
|
|
available = false
|
|
in = false
|
|
out = true
|
|
pool = false
|
|
failed = false
|
|
case "pool":
|
|
available = false
|
|
in = false
|
|
out = false
|
|
pool = true
|
|
failed = false
|
|
case "failed":
|
|
available = false
|
|
in = false
|
|
out = false
|
|
pool = false
|
|
failed = true
|
|
|
|
}
|
|
}
|
|
|
|
if len(line_parts) >= 3 { // user supplied min height
|
|
s, err := strconv.ParseUint(line_parts[2], 10, 64)
|
|
if err != nil {
|
|
globals.Logger.Warnf("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 {
|
|
globals.Logger.Warnf("Error parsing maximum height")
|
|
return
|
|
}
|
|
max_height = s
|
|
}
|
|
|
|
// request payments without payment id
|
|
transfers := wallet.Show_Transfers(available, in, out, pool, failed, false, min_height, max_height) // receives sorted list of transfers
|
|
|
|
if len(transfers) == 0 {
|
|
globals.Logger.Warnf("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 if len(transfers[i].PaymentID) == 0 {
|
|
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.FormatMoney(transfers[i].Amount)))
|
|
} else {
|
|
payment_id := fmt.Sprintf("%x", transfers[i].PaymentID)
|
|
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.FormatMoney(transfers[i].Amount), payment_id))
|
|
}
|
|
|
|
case 1:
|
|
payment_id := fmt.Sprintf("%x", transfers[i].PaymentID)
|
|
io.WriteString(l.Stderr(), fmt.Sprintf(color_magenta+"%s Height %d TopoHeight %d transaction %s spent %s DERO"+color_white+" PAYMENT ID: %s Proof:%s\n", transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney(transfers[i].Amount), payment_id, transfers[i].Proof))
|
|
case 2:
|
|
fallthrough
|
|
default:
|
|
globals.Logger.Warnf("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
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|