337 lines
11 KiB
Go
337 lines
11 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 "io"
|
|
import "os"
|
|
import "fmt"
|
|
import "io/ioutil"
|
|
import "strings"
|
|
import "path/filepath"
|
|
import "encoding/hex"
|
|
|
|
import "github.com/chzyer/readline"
|
|
|
|
import "github.com/deroproject/derohe/globals"
|
|
import "github.com/deroproject/derohe/address"
|
|
|
|
//import "github.com/deroproject/derohe/walletapi"
|
|
import "github.com/deroproject/derohe/transaction"
|
|
|
|
// handle menu if a wallet is currently opened
|
|
func display_easymenu_post_open_command(l *readline.Instance) {
|
|
w := l.Stderr()
|
|
io.WriteString(w, "Menu:\n")
|
|
|
|
io.WriteString(w, "\t\033[1m1\033[0m\tDisplay account Address \n")
|
|
io.WriteString(w, "\t\033[1m2\033[0m\tDisplay Seed "+color_red+"(Please save seed in safe location)\n\033[0m")
|
|
|
|
io.WriteString(w, "\t\033[1m3\033[0m\tDisplay Keys (hex)\n")
|
|
|
|
if !is_registered(wallet) {
|
|
io.WriteString(w, "\t\033[1m4\033[0m\tAccount registration to blockchain (registration has no fee requirement and is precondition to use the account)\n")
|
|
io.WriteString(w, "\n")
|
|
io.WriteString(w, "\n")
|
|
} else { // hide some commands, if view only wallet
|
|
io.WriteString(w, "\n")
|
|
io.WriteString(w, "\t\033[1m5\033[0m\tTransfer (send DERO) To Another Wallet\n")
|
|
//io.WriteString(w, "\t\033[1m6\033[0m\tCreate Transaction in offline mode\n")
|
|
io.WriteString(w, "\n")
|
|
}
|
|
|
|
io.WriteString(w, "\t\033[1m7\033[0m\tChange wallet password\n")
|
|
io.WriteString(w, "\t\033[1m8\033[0m\tClose Wallet\n")
|
|
if is_registered(wallet) {
|
|
io.WriteString(w, "\t\033[1m12\033[0m\tTransfer all balance (send DERO) To Another Wallet\n")
|
|
io.WriteString(w, "\t\033[1m13\033[0m\tShow transaction history\n")
|
|
io.WriteString(w, "\t\033[1m14\033[0m\tRescan transaction history\n")
|
|
}
|
|
|
|
io.WriteString(w, "\n\t\033[1m9\033[0m\tExit menu and start prompt\n")
|
|
io.WriteString(w, "\t\033[1m0\033[0m\tExit Wallet\n")
|
|
|
|
}
|
|
|
|
// this handles all the commands if wallet in menu mode and a wallet is opened
|
|
func handle_easymenu_post_open_command(l *readline.Instance, line string) (processed bool) {
|
|
|
|
var err error
|
|
_ = err
|
|
line = strings.TrimSpace(line)
|
|
line_parts := strings.Fields(line)
|
|
processed = true
|
|
|
|
if len(line_parts) < 1 { // if no command return
|
|
return
|
|
}
|
|
|
|
command := ""
|
|
if len(line_parts) >= 1 {
|
|
command = strings.ToLower(line_parts[0])
|
|
}
|
|
|
|
offline_tx := false
|
|
_ = offline_tx
|
|
switch command {
|
|
case "1":
|
|
fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+"\n", wallet.GetAddress())
|
|
|
|
if !is_registered(wallet) {
|
|
reg_tx := wallet.GetRegistrationTX()
|
|
fmt.Fprintf(l.Stderr(), "Registration TX : "+color_green+"%x"+color_white+"\n", reg_tx.Serialize())
|
|
}
|
|
PressAnyKey(l, wallet)
|
|
|
|
case "2": // give user his seed
|
|
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
|
|
PressAnyKey(l, wallet)
|
|
|
|
case "3": // give user his keys in hex form
|
|
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
globals.Logger.Warnf("Invalid password")
|
|
PressAnyKey(l, wallet)
|
|
break
|
|
}
|
|
|
|
display_spend_key(l, wallet)
|
|
PressAnyKey(l, wallet)
|
|
|
|
case "4": // Registration
|
|
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
globals.Logger.Warnf("Invalid password")
|
|
PressAnyKey(l, wallet)
|
|
break
|
|
}
|
|
|
|
//if valid_registration_or_display_error(l, wallet) {
|
|
// globals.Logger.Warnf("This wallet address is already registered.")
|
|
// break
|
|
//}
|
|
fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+" is going to be registered.This is a pre-condition for using the online chain.It will take few seconds to register/", wallet.GetAddress())
|
|
|
|
reg_tx := wallet.GetRegistrationTX()
|
|
|
|
if ConfirmYesNoDefaultNo(l, "Confirm Registration (y/N)") {
|
|
// at this point we must send the registration transaction
|
|
|
|
fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+" is going to be registered.Pls wait till the account is registered.", wallet.GetAddress())
|
|
|
|
wallet.SendTransaction(reg_tx)
|
|
|
|
}
|
|
|
|
case "6":
|
|
offline_tx = true
|
|
fallthrough
|
|
case "5":
|
|
if !valid_registration_or_display_error(l, wallet) {
|
|
break
|
|
}
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
globals.Logger.Warnf("Invalid password")
|
|
break
|
|
}
|
|
|
|
// a , amount_to_transfer, err := collect_transfer_info(l,wallet)
|
|
a, err := ReadAddress(l)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Err :%s", err)
|
|
break
|
|
}
|
|
|
|
amount_str := read_line_with_prompt(l, fmt.Sprintf("Enter amount to transfer in DERO (max TODO .00004 hard coded): "))
|
|
|
|
if amount_str == "" {
|
|
amount_str = ".00009"
|
|
}
|
|
amount_to_transfer, err := globals.ParseAmount(amount_str)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Err :%s", err)
|
|
break // invalid amount provided, bail out
|
|
}
|
|
|
|
var payment_id []byte
|
|
_ = payment_id
|
|
// if user provided an integrated address donot ask him payment id
|
|
if a.IsIntegratedAddress() {
|
|
globals.Logger.Infof("Payment ID is integreted in address ID:%x", a.PaymentID)
|
|
}
|
|
|
|
if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") {
|
|
addr_list := []address.Address{*a}
|
|
amount_list := []uint64{amount_to_transfer} // transfer 50 dero, 2 dero
|
|
fees_per_kb := uint64(0) // fees must be calculated by walletapi
|
|
tx, err := wallet.Transfer(addr_list, amount_list, 0, hex.EncodeToString(payment_id), fees_per_kb, 0, false)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Error while building Transaction err %s\n", err)
|
|
break
|
|
}
|
|
build_relay_transaction(l, tx, err, offline_tx, amount_list)
|
|
}
|
|
|
|
case "12":
|
|
if !valid_registration_or_display_error(l, wallet) {
|
|
break
|
|
}
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
globals.Logger.Warnf("Invalid password")
|
|
break
|
|
}
|
|
|
|
// a , amount_to_transfer, err := collect_transfer_info(l,wallet)
|
|
fmt.Printf("dest address %s\n", "deroi1qxqqkmaz8nhv4q07w3cjyt84kmrqnuw4nprpqfl9xmmvtvwa7cdykxq5dph4ufnx5ndq4ltraf (14686f5e2666a4da) dero1qxqqkmaz8nhv4q07w3cjyt84kmrqnuw4nprpqfl9xmmvtvwa7cdykxqpfpaes")
|
|
a, err := ReadAddress(l)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Err :%s", err)
|
|
break
|
|
}
|
|
// if user provided an integrated address donot ask him payment id
|
|
if a.IsIntegratedAddress() {
|
|
globals.Logger.Infof("Payment ID is integreted in address ID:%x", a.PaymentID)
|
|
}
|
|
|
|
if ConfirmYesNoDefaultNo(l, "Confirm Transaction to send entire balance (y/N)") {
|
|
|
|
addr_list := []address.Address{*a}
|
|
amount_list := []uint64{0} // transfer 50 dero, 2 dero
|
|
fees_per_kb := uint64(0) // fees must be calculated by walletapi
|
|
tx, err := wallet.Transfer(addr_list, amount_list, 0, "", fees_per_kb, 0, true)
|
|
if err != nil {
|
|
globals.Logger.Warnf("Error while building Transaction err %s\n", err)
|
|
break
|
|
}
|
|
|
|
build_relay_transaction(l, tx, err, offline_tx, amount_list)
|
|
}
|
|
|
|
//PressAnyKey(l, wallet) // wait for a key press
|
|
|
|
case "7": // change 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 "8": // close and discard user key
|
|
|
|
wallet.Close_Encrypted_Wallet()
|
|
prompt_mutex.Lock()
|
|
wallet = nil // overwrite previous instance
|
|
prompt_mutex.Unlock()
|
|
|
|
fmt.Fprintf(l.Stderr(), color_yellow+"Wallet closed"+color_white)
|
|
|
|
case "9": // enable prompt mode
|
|
menu_mode = false
|
|
globals.Logger.Infof("Prompt mode enabled, type \"menu\" command to start menu mode")
|
|
|
|
case "0", "bye", "exit", "quit":
|
|
wallet.Close_Encrypted_Wallet() // save the wallet
|
|
prompt_mutex.Lock()
|
|
wallet = nil
|
|
globals.Exit_In_Progress = true
|
|
prompt_mutex.Unlock()
|
|
fmt.Fprintf(l.Stderr(), color_yellow+"Wallet closed"+color_white)
|
|
fmt.Fprintf(l.Stderr(), color_yellow+"Exiting"+color_white)
|
|
|
|
case "13":
|
|
show_transfers(l, wallet, 100)
|
|
|
|
case "14":
|
|
globals.Logger.Infof("Rescanning wallet history")
|
|
rescan_bc(wallet)
|
|
|
|
default:
|
|
processed = false // just loop
|
|
|
|
}
|
|
return
|
|
}
|
|
|
|
// handles the output after building tx, takes feedback, confirms or relays tx
|
|
func build_relay_transaction(l *readline.Instance, tx *transaction.Transaction, err error, offline_tx bool, amount_list []uint64) {
|
|
|
|
if err != nil {
|
|
globals.Logger.Warnf("Error while building Transaction err %s\n", err)
|
|
return
|
|
}
|
|
amount := uint64(0)
|
|
for i := range amount_list {
|
|
amount += amount_list[i]
|
|
}
|
|
globals.Logger.Infof("Transfering total amount %s DERO", globals.FormatMoney(amount))
|
|
|
|
globals.Logger.Infof("fees %s DERO", globals.FormatMoney(tx.Statement.Fees))
|
|
globals.Logger.Infof("TX Size %0.1f KiB", float32(len(tx.Serialize()))/1024.0)
|
|
|
|
//if input_sum != (amount + change + tx.Fee()) {
|
|
// panic(fmt.Sprintf("Inputs %d != outputs ( %d + %d + %d )", input_sum, amount, change, tx.RctSignature.Get_TX_Fee()))
|
|
//}
|
|
|
|
//if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") {
|
|
if true {
|
|
|
|
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")
|
|
globals.Logger.Infof("Transaction discarded")
|
|
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)
|
|
}
|
|
|
|
}
|
|
|
|
PressAnyKey(l, wallet) // wait for a key press
|
|
} else {
|
|
globals.Logger.Infof("Transaction discarded")
|
|
}
|
|
|
|
}
|