439 lines
15 KiB
Go
439 lines
15 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 "time"
|
|
import "fmt"
|
|
|
|
//import "io/ioutil"
|
|
import "strings"
|
|
|
|
//import "path/filepath"
|
|
//import "encoding/hex"
|
|
|
|
import "github.com/chzyer/readline"
|
|
|
|
import "github.com/deroproject/derohe/rpc"
|
|
import "github.com/deroproject/derohe/globals"
|
|
|
|
//import "github.com/deroproject/derohe/address"
|
|
|
|
import "github.com/deroproject/derohe/cryptography/crypto"
|
|
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 !wallet.IsRegistered() {
|
|
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, "\t\033[1m4\033[0m\tDisplay wallet pool\n")
|
|
io.WriteString(w, "\t\033[1m5\033[0m\tTransfer (send DERO) to Another Wallet\n")
|
|
io.WriteString(w, "\t\033[1m6\033[0m\tToken transfer to another wallet\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 wallet.IsRegistered() {
|
|
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 !wallet.IsRegistered() {
|
|
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) {
|
|
logger.Error(fmt.Errorf("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) {
|
|
logger.Error(fmt.Errorf("Invalid password"), "")
|
|
PressAnyKey(l, wallet)
|
|
break
|
|
}
|
|
|
|
display_spend_key(l, wallet)
|
|
PressAnyKey(l, wallet)
|
|
|
|
case "4": // Registration
|
|
|
|
if !wallet.IsRegistered() {
|
|
|
|
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.\n", wallet.GetAddress())
|
|
|
|
reg_tx := wallet.GetRegistrationTX()
|
|
|
|
// 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.\n", wallet.GetAddress())
|
|
err := wallet.SendTransaction(reg_tx)
|
|
if err != nil {
|
|
fmt.Fprintf(l.Stderr(), "sending registration tx err %s\n", err)
|
|
} else {
|
|
fmt.Fprintf(l.Stderr(), "registration tx dispatched successfully\n")
|
|
}
|
|
|
|
} else {
|
|
|
|
}
|
|
|
|
case "6":
|
|
if !valid_registration_or_display_error(l, wallet) {
|
|
break
|
|
}
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
logger.Error(fmt.Errorf("Invalid password"), "")
|
|
break
|
|
}
|
|
|
|
scid, err := ReadSCID(l)
|
|
if err != nil {
|
|
logger.Error(err, "error reading SCID")
|
|
break
|
|
}
|
|
|
|
a, err := ReadAddress(l, wallet)
|
|
if err != nil {
|
|
logger.Error(err, "error reading address")
|
|
break
|
|
}
|
|
|
|
var amount_to_transfer uint64
|
|
|
|
amount_str := read_line_with_prompt(l, fmt.Sprintf("Enter token amount to transfer in SCID (max TODO): "))
|
|
if amount_str == "" {
|
|
amount_str = ".00001"
|
|
}
|
|
amount_to_transfer, err = globals.ParseAmount(amount_str)
|
|
if err != nil {
|
|
logger.Error(err, "Err parsing amount")
|
|
break // invalid amount provided, bail out
|
|
}
|
|
|
|
if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") {
|
|
tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{SCID: scid, Amount: amount_to_transfer, Destination: a.String()}}, 0, false, rpc.Arguments{}, 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")
|
|
break
|
|
}
|
|
logger.Info("Dispatched tx", "txid", tx.GetHash().String())
|
|
}
|
|
|
|
case "5":
|
|
if !valid_registration_or_display_error(l, wallet) {
|
|
break
|
|
}
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
logger.Error(fmt.Errorf("Invalid password"), "")
|
|
break
|
|
}
|
|
|
|
// a , amount_to_transfer, err := collect_transfer_info(l,wallet)
|
|
a, err := ReadAddress(l, wallet)
|
|
if err != nil {
|
|
logger.Error(err, "error reading address")
|
|
break
|
|
}
|
|
|
|
var amount_to_transfer uint64
|
|
|
|
var arguments = rpc.Arguments{
|
|
// { rpc.RPC_DESTINATION_PORT, rpc.DataUint64,uint64(0x1234567812345678)},
|
|
// { rpc.RPC_VALUE_TRANSFER, rpc.DataUint64,uint64(12345)},
|
|
// { rpc.RPC_EXPIRY , rpc.DataTime, time.Now().Add(time.Hour).UTC()},
|
|
// { rpc.RPC_COMMENT , rpc.DataString, "Purchase XYZ"},
|
|
}
|
|
if a.IsIntegratedAddress() { // read everything from the address
|
|
|
|
if a.Arguments.Validate_Arguments() != nil {
|
|
logger.Error(err, "Integrated Address arguments could not be validated.")
|
|
break
|
|
}
|
|
|
|
if !a.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { // but only it is present
|
|
logger.Error(fmt.Errorf("Integrated Address does not contain destination port."), "")
|
|
break
|
|
}
|
|
|
|
arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)})
|
|
// arguments = append(arguments, rpc.Argument{"Comment", rpc.DataString, "holygrail of all data is now working if you can see this"})
|
|
|
|
if a.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { // but only it is present
|
|
|
|
if a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) {
|
|
logger.Error(nil, "This address has expired.", "expiry time", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime))
|
|
break
|
|
} else {
|
|
logger.Info("This address will expire ", "expiry time", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime))
|
|
}
|
|
}
|
|
|
|
logger.Info("Destination port is integreted in address.", "dst port", a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64))
|
|
|
|
if a.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { // but only it is present
|
|
logger.Info("Integrated Message", "comment", a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString))
|
|
}
|
|
}
|
|
|
|
// arguments have been already validated
|
|
for _, arg := range a.Arguments {
|
|
if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) {
|
|
switch arg.DataType {
|
|
case rpc.DataString:
|
|
if v, err := ReadString(l, arg.Name, arg.Value.(string)); err == nil {
|
|
arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v})
|
|
} else {
|
|
logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "")
|
|
return
|
|
}
|
|
case rpc.DataInt64:
|
|
if v, err := ReadInt64(l, arg.Name, arg.Value.(int64)); err == nil {
|
|
arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v})
|
|
} else {
|
|
logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "")
|
|
return
|
|
}
|
|
case rpc.DataUint64:
|
|
if v, err := ReadUint64(l, arg.Name, arg.Value.(uint64)); err == nil {
|
|
arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v})
|
|
} else {
|
|
logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "")
|
|
return
|
|
}
|
|
case rpc.DataFloat64:
|
|
if v, err := ReadFloat64(l, arg.Name, arg.Value.(float64)); err == nil {
|
|
arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v})
|
|
} else {
|
|
logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "")
|
|
return
|
|
}
|
|
case rpc.DataTime:
|
|
logger.Error(fmt.Errorf("time argument is currently not supported."), "")
|
|
break
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if a.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { // but only it is present
|
|
logger.Info("Transaction", "Value", globals.FormatMoney(a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64)))
|
|
amount_to_transfer = a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64)
|
|
} else {
|
|
|
|
amount_str := read_line_with_prompt(l, fmt.Sprintf("Enter amount to transfer in DERO (max TODO): "))
|
|
|
|
if amount_str == "" {
|
|
amount_str = ".00001"
|
|
}
|
|
amount_to_transfer, err = globals.ParseAmount(amount_str)
|
|
if err != nil {
|
|
logger.Error(err, "Err parsing amount")
|
|
break // invalid amount provided, bail out
|
|
}
|
|
}
|
|
|
|
// check whether the service needs the address of sender
|
|
// this is required to enable services which are completely invisisble to external entities
|
|
// external entities means anyone except sender/receiver
|
|
if a.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataUint64) {
|
|
logger.Info("This RPC has requested your address.")
|
|
logger.Info("If you are expecting something back, it needs to be sent")
|
|
logger.Info("Your address will remain completely invisible to external entities(only sender/receiver can see your address)")
|
|
arguments = append(arguments, rpc.Argument{Name: rpc.RPC_REPLYBACK_ADDRESS, DataType: rpc.DataAddress, Value: wallet.GetAddress()})
|
|
}
|
|
|
|
// if no arguments, use space by embedding a small comment
|
|
if len(arguments) == 0 { // allow user to enter Comment
|
|
if v, err := ReadString(l, "Comment", ""); err == nil {
|
|
arguments = append(arguments, rpc.Argument{Name: "Comment", DataType: rpc.DataString, Value: v})
|
|
} else {
|
|
logger.Error(fmt.Errorf("%s could not be parsed (type %s),", "Comment", rpc.DataString), "")
|
|
return
|
|
}
|
|
}
|
|
|
|
if _, err := arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil {
|
|
logger.Error(err, "Arguments packing err")
|
|
return
|
|
}
|
|
|
|
if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") {
|
|
|
|
//src_port := uint64(0xffffffffffffffff)
|
|
|
|
tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{Amount: amount_to_transfer, Destination: a.String(), Payload_RPC: arguments}}, 0, false, rpc.Arguments{}, 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")
|
|
break
|
|
}
|
|
logger.Info("Dispatched tx", "txid", tx.GetHash().String())
|
|
//fmt.Printf("queued tx err %s\n")
|
|
}
|
|
|
|
case "12":
|
|
if !valid_registration_or_display_error(l, wallet) {
|
|
break
|
|
}
|
|
if !ValidateCurrentPassword(l, wallet) {
|
|
logger.Error(fmt.Errorf("Invalid password"), "")
|
|
break
|
|
}
|
|
|
|
logger.Error(err, "Not supported ")
|
|
|
|
/*
|
|
// 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
|
|
uid, err := wallet.PoolTransfer(addr_list, amount_list, fees_per_kb, 0, true)
|
|
_ = uid
|
|
if err != nil {
|
|
globals.Logger.Warnf("Error while building Transaction err %s\n", err)
|
|
break
|
|
}
|
|
}
|
|
*/
|
|
|
|
//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 {
|
|
logger.Info("Wallet password successfully changed")
|
|
} else {
|
|
logger.Error(err, "Wallet password could not be changed")
|
|
}
|
|
}
|
|
|
|
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
|
|
logger.Info("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":
|
|
var zeroscid crypto.Hash
|
|
show_transfers(l, wallet, zeroscid, 100)
|
|
|
|
case "14":
|
|
logger.Info("Rescanning wallet history")
|
|
rescan_bc(wallet)
|
|
|
|
default:
|
|
processed = false // just loop
|
|
|
|
}
|
|
return
|
|
}
|