github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/cmd/dero-wallet-cli/prompt.go (about) 1 // Copyright 2017-2018 DERO Project. All rights reserved. 2 // Use of this source code in any form is governed by RESEARCH license. 3 // license can be found in the LICENSE file. 4 // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 5 // 6 // 7 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 8 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 9 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 10 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 12 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 13 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 14 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 15 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 17 package main 18 19 import "os" 20 import "io" 21 import "fmt" 22 import "bytes" 23 import "time" 24 import "io/ioutil" 25 import "path/filepath" 26 import "strings" 27 import "strconv" 28 import "encoding/hex" 29 30 import "github.com/chzyer/readline" 31 32 import "github.com/deroproject/derosuite/config" 33 import "github.com/deroproject/derosuite/crypto" 34 import "github.com/deroproject/derosuite/globals" 35 import "github.com/deroproject/derosuite/address" 36 import "github.com/deroproject/derosuite/walletapi" 37 38 var account walletapi.Account 39 40 // handle all commands while in prompt mode 41 func handle_prompt_command(l *readline.Instance, line string) { 42 43 var err error 44 line = strings.TrimSpace(line) 45 line_parts := strings.Fields(line) 46 47 if len(line_parts) < 1 { // if no command return 48 return 49 } 50 51 _ = err 52 command := "" 53 if len(line_parts) >= 1 { 54 command = strings.ToLower(line_parts[0]) 55 } 56 57 // handled closed wallet commands 58 switch command { 59 case "address", "rescan_bc", "rescan_spent", "seed", "set", "password", "get_tx_key", "i8", "i32", "payment_id": 60 fallthrough 61 case "spendkey", "viewkey", "transfer", "locked_transfer", "close": 62 fallthrough 63 case "transfer_all", "sweep_all", "show_transfers", "balance", "status": 64 if wallet == nil { 65 globals.Logger.Warnf("No wallet available") 66 return 67 } 68 } 69 70 locked_to_height := uint64(0) 71 switch command { 72 case "help": 73 usage(l.Stderr()) 74 case "address": // give user his account address 75 76 fmt.Fprintf(l.Stderr(), "Wallet address : "+color_green+"%s"+color_white+"\n", wallet.GetAddress()) 77 case "status": // show syncronisation status 78 fmt.Fprintf(l.Stderr(), "Wallet Version : %s\n", config.Version.String()) 79 fmt.Fprintf(l.Stderr(), "Wallet Height : %d\t Daemon Height %d \n", wallet.Get_Height(), wallet.Get_Daemon_Height()) 80 fallthrough 81 case "balance": // give user his balance 82 balance_unlocked, locked_balance := wallet.Get_Balance_Rescan() 83 fmt.Fprintf(l.Stderr(), "Locked balance : "+color_green+"%s"+color_white+"\n", globals.FormatMoney12(locked_balance)) 84 fmt.Fprintf(l.Stderr(), "Unlocked balance : "+color_green+"%s"+color_white+"\n", globals.FormatMoney12(balance_unlocked)) 85 fmt.Fprintf(l.Stderr(), "Total balance : "+color_green+"%s"+color_white+"\n\n", globals.FormatMoney12(locked_balance+balance_unlocked)) 86 87 case "rescan_bc", "rescan_spent": // rescan from 0 88 if offline_mode { 89 globals.Logger.Warnf("Offline wallet rescanning NOT implemented") 90 } else { 91 rescan_bc(wallet) 92 } 93 94 case "seed": // give user his seed, if password is valid 95 if !wallet.Is_View_Only() { 96 if !ValidateCurrentPassword(l, wallet) { 97 globals.Logger.Warnf("Invalid password") 98 PressAnyKey(l, wallet) 99 break 100 } 101 display_seed(l, wallet) // seed should be given only to authenticated users 102 } else { 103 fmt.Fprintf(l.Stderr(), color_red+" View wallet do not have seeds"+color_white) 104 } 105 case "spendkey": // give user his spend key 106 if wallet.Is_View_Only() { 107 fmt.Fprintf(l.Stderr(), "View wallet do not have spend key") 108 return 109 } 110 111 display_spend_key(l, wallet) 112 case "viewkey": // give user his viewkey 113 if !ValidateCurrentPassword(l, wallet) { 114 globals.Logger.Warnf("Invalid password") 115 break 116 } 117 display_view_key(l, wallet) 118 case "walletviewkey": 119 if !ValidateCurrentPassword(l, wallet) { 120 globals.Logger.Warnf("Invalid password") 121 break 122 } 123 display_viewwallet_key(l, wallet) 124 125 case "password": // change wallet password 126 if ConfirmYesNoDefaultNo(l, "Change wallet password (y/N)") && 127 ValidateCurrentPassword(l, wallet) { 128 129 new_password := ReadConfirmedPassword(l, "Enter new password", "Confirm password") 130 err = wallet.Set_Encrypted_Wallet_Password(new_password) 131 if err == nil { 132 globals.Logger.Infof("Wallet password successfully changed") 133 } else { 134 globals.Logger.Warnf("Wallet password could not be changed err %s", err) 135 } 136 } 137 138 case "get_tx_key": 139 if len(line_parts) == 2 && len(line_parts[1]) == 64 { 140 _, err := hex.DecodeString(line_parts[1]) 141 if err != nil { 142 globals.Logger.Warnf("Error parsing txhash") 143 break 144 } 145 key := wallet.GetTXKey(crypto.HexToHash(line_parts[1])) 146 if key != "" { 147 globals.Logger.Infof("TX secret key \"%s\"", key) 148 } else { 149 globals.Logger.Warnf("TX not found in database") 150 } 151 } else { 152 globals.Logger.Warnf("get_tx_key needs transaction hash as input parameter") 153 globals.Logger.Warnf("eg. get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7") 154 } 155 case "sweep_all", "transfer_all": // transfer everything 156 Transfer_Everything(l) 157 158 case "show_transfers": 159 show_transfers(l, wallet, 100) 160 case "set": // set/display different settings 161 handle_set_command(l, line) 162 case "close": // close the account 163 if !ValidateCurrentPassword(l, wallet) { 164 globals.Logger.Warnf("Invalid password") 165 break 166 } 167 wallet.Close_Encrypted_Wallet() // overwrite previous instance 168 169 case "menu": // enable menu mode 170 menu_mode = true 171 globals.Logger.Infof("Menu mode enabled") 172 case "i8", "integrated_address": // user wants a random integrated address 8 bytes 173 a := wallet.GetRandomIAddress8() 174 fmt.Fprintf(l.Stderr(), "Wallet integrated address : "+color_green+"%s"+color_white+"\n", a.String()) 175 fmt.Fprintf(l.Stderr(), "Embedded payment ID : "+color_green+"%x"+color_white+"\n", a.PaymentID) 176 177 case "i32", "integrated_address32": 178 a := wallet.GetRandomIAddress32() 179 fmt.Fprintf(l.Stderr(), "Wallet integrated address : "+color_green+"%s"+color_white+"\n", a.String()) 180 fmt.Fprintf(l.Stderr(), "Embedded 32 byte payment ID : "+color_green+"%x"+color_white+"\n", a.PaymentID) 181 case "payment_id": 182 a := wallet.GetRandomIAddress32() 183 fmt.Fprintf(l.Stderr(), "Random 32 byte payment ID : "+color_green+"%x"+color_white+"\n", a.PaymentID) 184 185 case "version": 186 globals.Logger.Infof("Version %s\n", config.Version.String()) 187 case "locked_transfer": // parse locked to height 188 _ = locked_to_height 189 190 case "transfer": 191 // parse the address, amount pair 192 line_parts := line_parts[1:] // remove first part 193 194 addr_list := []address.Address{} 195 amount_list := []uint64{} 196 payment_id := "" 197 198 for i := 0; i < len(line_parts); { 199 200 globals.Logger.Debugf("len %d %+v", len(line_parts), line_parts) 201 if len(line_parts) >= 2 { // parse address amount pair 202 addr, err := globals.ParseValidateAddress(line_parts[0]) 203 if err != nil { 204 globals.Logger.Warnf("Error Parsing \"%s\" err %s", line_parts[0], err) 205 return 206 } 207 amount, err := globals.ParseAmount(line_parts[1]) 208 if err != nil { 209 globals.Logger.Warnf("Error Parsing \"%s\" err %s", line_parts[1], err) 210 return 211 } 212 line_parts = line_parts[2:] // remove parsed 213 214 addr_list = append(addr_list, *addr) 215 amount_list = append(amount_list, amount) 216 217 continue 218 } 219 if len(line_parts) == 1 { // parse payment_id 220 if len(line_parts[0]) == 64 || len(line_parts[0]) == 16 { 221 _, err := hex.DecodeString(line_parts[0]) 222 if err != nil { 223 globals.Logger.Warnf("Error parsing payment ID, it should be in hex 16 or 64 chars") 224 return 225 } 226 payment_id = line_parts[0] 227 line_parts = line_parts[1:] 228 229 } else { 230 globals.Logger.Warnf("Invalid payment ID \"%s\"", line_parts[0]) 231 return 232 } 233 234 } 235 } 236 237 // check if everything is okay, if yes build the transaction 238 if len(addr_list) == 0 { 239 globals.Logger.Warnf("Destination address not provided") 240 return 241 } 242 243 payment_id_integrated := false 244 for i := range addr_list { 245 if addr_list[i].IsIntegratedAddress() { 246 payment_id_integrated = true 247 globals.Logger.Infof("Payment ID is integreted in address ID:%x", addr_list[i].PaymentID) 248 } 249 250 } 251 252 // if user provided an integrated address donot ask him payment id 253 // otherwise confirm whether user wants to send without payment id 254 if payment_id_integrated == false && len(payment_id) == 0 { 255 payment_id_bytes, err := ReadPaymentID(l) 256 payment_id = hex.EncodeToString(payment_id_bytes) 257 if err != nil { 258 globals.Logger.Warnf("Err :%s", err) 259 break 260 } 261 } 262 263 offline := false 264 tx, inputs, input_sum, change, err := wallet.Transfer(addr_list, amount_list, 0, payment_id, 0, 0) 265 build_relay_transaction(l, tx, inputs, input_sum, change, err, offline, amount_list) 266 267 case "q", "bye", "exit", "quit": 268 globals.Exit_In_Progress = true 269 if wallet != nil { 270 wallet.Close_Encrypted_Wallet() // overwrite previous instance 271 } 272 273 case "": // blank enter key just loop 274 default: 275 //fmt.Fprintf(l.Stderr(), "you said: %s", strconv.Quote(line)) 276 globals.Logger.Warnf("No such command") 277 } 278 279 } 280 281 // handle all commands while in prompt mode 282 func handle_set_command(l *readline.Instance, line string) { 283 284 //var err error 285 line = strings.TrimSpace(line) 286 line_parts := strings.Fields(line) 287 288 if len(line_parts) < 1 { // if no command return 289 return 290 } 291 292 command := "" 293 if len(line_parts) >= 2 { 294 command = strings.ToLower(line_parts[1]) 295 } 296 297 help := false 298 switch command { 299 case "help": 300 case "mixin": 301 if len(line_parts) != 3 { 302 globals.Logger.Warnf("Wrong number of arguments, see help eg") 303 help = true 304 break 305 } 306 s, err := strconv.ParseUint(line_parts[2], 10, 64) 307 if err != nil { 308 globals.Logger.Warnf("Error parsing mixin") 309 return 310 } 311 wallet.SetMixin(int(s)) 312 globals.Logger.Infof("Mixin = %d", wallet.GetMixin()) 313 314 case "priority": 315 if len(line_parts) != 3 { 316 globals.Logger.Warnf("Wrong number of arguments, see help eg") 317 help = true 318 break 319 } 320 s, err := strconv.ParseFloat(line_parts[2], 64) 321 if err != nil { 322 globals.Logger.Warnf("Error parsing priority") 323 return 324 } 325 wallet.SetFeeMultiplier(float32(s)) 326 globals.Logger.Infof("Transaction priority = %.02f", wallet.GetFeeMultiplier()) 327 328 case "seed": // seed only has 1 setting, lanuage so do it now 329 language := choose_seed_language(l) 330 globals.Logger.Infof("Setting seed language to \"%s\"", wallet.SetSeedLanguage(language)) 331 332 default: 333 help = true 334 } 335 336 if help == true || len(line_parts) == 1 { // user type plain set command, give out all settings and help 337 338 fmt.Fprintf(l.Stderr(), color_extra_white+"Current settings"+color_extra_white+"\n") 339 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()) 340 fmt.Fprintf(l.Stderr(), color_normal+"Mixin: "+color_extra_white+"%d\t"+color_normal+"eg. "+color_extra_white+"set mixin 13\n"+color_normal, wallet.GetMixin()) 341 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()) 342 fmt.Fprintf(l.Stderr(), "\t\tMinimum priority is 1.00. High priority = high fees\n") 343 344 } 345 } 346 347 func Transfer_Everything(l *readline.Instance) { 348 if wallet.Is_View_Only() { 349 fmt.Fprintf(l.Stderr(), color_yellow+"View Only wallet cannot transfer."+color_white) 350 } 351 352 if !ValidateCurrentPassword(l, wallet) { 353 globals.Logger.Warnf("Invalid password") 354 return 355 } 356 357 // a , amount_to_transfer, err := collect_transfer_info(l,wallet) 358 addr, err := ReadAddress(l) 359 if err != nil { 360 globals.Logger.Warnf("Err :%s", err) 361 return 362 } 363 364 var payment_id []byte 365 // if user provided an integrated address donot ask him payment id 366 if !addr.IsIntegratedAddress() { 367 payment_id, err = ReadPaymentID(l) 368 if err != nil { 369 globals.Logger.Warnf("Err :%s", err) 370 return 371 } 372 } else { 373 globals.Logger.Infof("Payment ID is integreted in address ID:%x", addr.PaymentID) 374 } 375 376 fees_per_kb := uint64(0) // fees must be calculated by walletapi 377 378 tx, inputs, input_sum, err := wallet.Transfer_Everything(*addr, hex.EncodeToString(payment_id), 0, fees_per_kb, 5) 379 380 _ = inputs 381 if err != nil { 382 globals.Logger.Warnf("Error while building Transaction err %s\n", err) 383 return 384 } 385 globals.Logger.Infof("%d Inputs Selected for %s DERO", len(inputs), globals.FormatMoney12(input_sum)) 386 globals.Logger.Infof("fees %s DERO", globals.FormatMoneyPrecision(tx.RctSignature.Get_TX_Fee(), 12)) 387 globals.Logger.Infof("TX Size %0.1f KiB (should be < 240 KiB)", float32(len(tx.Serialize()))/1024.0) 388 offline_tx := false 389 if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") { 390 391 if offline_tx { // if its an offline tx, dump it to a file 392 cur_dir, err := os.Getwd() 393 if err != nil { 394 globals.Logger.Warnf("Cannot obtain current directory to save tx") 395 return 396 } 397 filename := filepath.Join(cur_dir, tx.GetHash().String()+".tx") 398 err = ioutil.WriteFile(filename, []byte(hex.EncodeToString(tx.Serialize())), 0600) 399 if err == nil { 400 if err == nil { 401 globals.Logger.Infof("Transaction saved successfully. txid = %s", tx.GetHash()) 402 globals.Logger.Infof("Saved to %s", filename) 403 } else { 404 globals.Logger.Warnf("Error saving tx to %s , err %s", filename, err) 405 } 406 } 407 408 } else { 409 410 err = wallet.SendTransaction(tx) // relay tx to daemon/network 411 if err == nil { 412 globals.Logger.Infof("Transaction sent successfully. txid = %s", tx.GetHash()) 413 } else { 414 globals.Logger.Warnf("Transaction sending failed txid = %s, err %s", tx.GetHash(), err) 415 } 416 417 } 418 } 419 420 } 421 422 // read an address with all goodies such as color encoding and other things in prompt 423 func ReadAddress(l *readline.Instance) (a *address.Address, err error) { 424 setPasswordCfg := l.GenPasswordConfig() 425 setPasswordCfg.EnableMask = false 426 427 prompt_mutex.Lock() 428 defer prompt_mutex.Unlock() 429 430 setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { 431 error_message := "" 432 color := color_green 433 434 if len(line) >= 1 { 435 _, err := globals.ParseValidateAddress(string(line)) 436 if err != nil { 437 error_message = " " //err.Error() 438 } 439 } 440 441 if error_message != "" { 442 color = color_red // Should we display the error message here?? 443 l.SetPrompt(fmt.Sprintf("%sEnter Destination Address: ", color)) 444 } else { 445 l.SetPrompt(fmt.Sprintf("%sEnter Destination Address: ", color)) 446 447 } 448 449 l.Refresh() 450 return nil, 0, false 451 }) 452 453 line, err := l.ReadPasswordWithConfig(setPasswordCfg) 454 if err != nil { 455 return 456 } 457 a, err = globals.ParseValidateAddress(string(line)) 458 l.SetPrompt(prompt) 459 l.Refresh() 460 return 461 } 462 463 // read an payment with all goodies such as color encoding and other things in prompt 464 func ReadPaymentID(l *readline.Instance) (payment_id []byte, err error) { 465 setPasswordCfg := l.GenPasswordConfig() 466 setPasswordCfg.EnableMask = false 467 468 // ask user whether he want to enter a payment ID 469 470 if !ConfirmYesNoDefaultNo(l, "Provide Payment ID (y/N)") { // user doesnot want to provide payment it, skip 471 return 472 } 473 474 prompt_mutex.Lock() 475 defer prompt_mutex.Unlock() 476 477 setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { 478 error_message := "" 479 color := color_green 480 481 if len(line) >= 1 { 482 _, err := hex.DecodeString(string(line)) 483 if (len(line) == 16 || len(line) == 64) && err == nil { 484 error_message = "" 485 } else { 486 error_message = " " //err.Error() 487 } 488 } 489 490 if error_message != "" { 491 color = color_red // Should we display the error message here?? 492 l.SetPrompt(fmt.Sprintf("%sEnter Payment ID (16/64 hex char): ", color)) 493 } else { 494 l.SetPrompt(fmt.Sprintf("%sEnter Payment ID (16/64 hex char): ", color)) 495 496 } 497 498 l.Refresh() 499 return nil, 0, false 500 }) 501 502 line, err := l.ReadPasswordWithConfig(setPasswordCfg) 503 if err != nil { 504 return 505 } 506 payment_id, err = hex.DecodeString(string(line)) 507 if err != nil { 508 return 509 } 510 l.SetPrompt(prompt) 511 l.Refresh() 512 513 if len(payment_id) == 8 || len(payment_id) == 32 { 514 return 515 } 516 517 err = fmt.Errorf("Invalid Payment ID") 518 return 519 } 520 521 // confirms whether the user wants to confirm yes 522 func ConfirmYesNoDefaultYes(l *readline.Instance, prompt_temporary string) bool { 523 prompt_mutex.Lock() 524 defer prompt_mutex.Unlock() 525 526 l.SetPrompt(prompt_temporary) 527 line, err := l.Readline() 528 if err == readline.ErrInterrupt { 529 if len(line) == 0 { 530 globals.Logger.Infof("Ctrl-C received, Exiting\n") 531 os.Exit(0) 532 } 533 } else if err == io.EOF { 534 os.Exit(0) 535 } 536 l.SetPrompt(prompt) 537 l.Refresh() 538 539 if strings.TrimSpace(line) == "n" || strings.TrimSpace(line) == "N" { 540 return false 541 } 542 return true 543 } 544 545 // confirms whether the user wants to confirm NO 546 func ConfirmYesNoDefaultNo(l *readline.Instance, prompt_temporary string) bool { 547 prompt_mutex.Lock() 548 defer prompt_mutex.Unlock() 549 550 l.SetPrompt(prompt_temporary) 551 line, err := l.Readline() 552 if err == readline.ErrInterrupt { 553 if len(line) == 0 { 554 globals.Logger.Infof("Ctrl-C received, Exiting\n") 555 os.Exit(0) 556 } 557 } else if err == io.EOF { 558 os.Exit(0) 559 } 560 l.SetPrompt(prompt) 561 562 if strings.TrimSpace(line) == "y" || strings.TrimSpace(line) == "Y" { 563 return true 564 } 565 return false 566 } 567 568 // confirms whether user knows the current password for the wallet 569 // this is triggerred while transferring amount, changing settings and so on 570 func ValidateCurrentPassword(l *readline.Instance, wallet *walletapi.Wallet) bool { 571 prompt_mutex.Lock() 572 defer prompt_mutex.Unlock() 573 574 // if user requested wallet to be open/unlocked, keep it open 575 if globals.Arguments["--unlocked"].(bool) == true { 576 return true 577 } 578 579 setPasswordCfg := l.GenPasswordConfig() 580 setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { 581 l.SetPrompt(fmt.Sprintf("Enter current wallet password(%v): ", len(line))) 582 l.Refresh() 583 return nil, 0, false 584 }) 585 586 //pswd, err := l.ReadPassword("please enter your password: ") 587 pswd, err := l.ReadPasswordWithConfig(setPasswordCfg) 588 if err != nil { 589 return false 590 } 591 592 // something was read, check whether it's the password setup in the wallet 593 return wallet.Check_Password(string(pswd)) 594 } 595 596 // reads a password to open the wallet 597 func ReadPassword(l *readline.Instance, filename string) string { 598 prompt_mutex.Lock() 599 defer prompt_mutex.Unlock() 600 601 try_again: 602 setPasswordCfg := l.GenPasswordConfig() 603 setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { 604 l.SetPrompt(fmt.Sprintf("Enter wallet password for %s (%v): ", filename, len(line))) 605 l.Refresh() 606 return nil, 0, false 607 }) 608 609 //pswd, err := l.ReadPassword("please enter your password: ") 610 pswd, err := l.ReadPasswordWithConfig(setPasswordCfg) 611 if err != nil { 612 goto try_again 613 } 614 615 // something was read, check whether it's the password setup in the wallet 616 return string(pswd) 617 } 618 func ReadConfirmedPassword(l *readline.Instance, first_prompt string, second_prompt string) (password string) { 619 prompt_mutex.Lock() 620 defer prompt_mutex.Unlock() 621 622 for { 623 setPasswordCfg := l.GenPasswordConfig() 624 setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { 625 l.SetPrompt(fmt.Sprintf("%s(%v): ", first_prompt, len(line))) 626 l.Refresh() 627 return nil, 0, false 628 }) 629 630 password_bytes, err := l.ReadPasswordWithConfig(setPasswordCfg) 631 if err != nil { 632 //return 633 continue 634 } 635 636 setPasswordCfg = l.GenPasswordConfig() 637 setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { 638 l.SetPrompt(fmt.Sprintf("%s(%v): ", second_prompt, len(line))) 639 l.Refresh() 640 return nil, 0, false 641 }) 642 643 confirmed_bytes, err := l.ReadPasswordWithConfig(setPasswordCfg) 644 if err != nil { 645 //return 646 continue 647 } 648 649 if bytes.Equal(password_bytes, confirmed_bytes) { 650 password = string(password_bytes) 651 err = nil 652 return 653 } 654 655 globals.Logger.Warnf("Passwords mismatch.Retrying.") 656 } 657 658 } 659 660 // confirms user to press a key 661 // this is triggerred while transferring amount, changing settings and so on 662 func PressAnyKey(l *readline.Instance, wallet *walletapi.Wallet) { 663 664 prompt_mutex.Lock() 665 defer prompt_mutex.Unlock() 666 667 setPasswordCfg := l.GenPasswordConfig() 668 setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { 669 670 l.SetPrompt(fmt.Sprintf("Press ENTER key to continue...")) 671 l.Refresh() 672 673 return nil, 0, false 674 }) 675 676 // any error or any key is the same 677 l.ReadPasswordWithConfig(setPasswordCfg) 678 679 return 680 } 681 682 /* 683 // if we are in offline, scan default or user provided file 684 // this function will replay the blockchain data in offline mode 685 func trigger_offline_data_scan() { 686 filename := default_offline_datafile 687 688 if globals.Arguments["--offline_datafile"] != nil { 689 filename = globals.Arguments["--offline_datafile"].(string) 690 } 691 692 f, err := os.Open(filename) 693 if err != nil { 694 globals.Logger.Warnf("Cannot read offline data file=\"%s\" err: %s ", filename, err) 695 return 696 } 697 w := bufio.NewReader(f) 698 gzipreader, err := gzip.NewReader(w) 699 if err != nil { 700 globals.Logger.Warnf("Error while decompressing offline data file=\"%s\" err: %s ", filename, err) 701 return 702 } 703 defer gzipreader.Close() 704 io.Copy(pipe_writer, gzipreader) 705 } 706 */ 707 708 // this completer is used to complete the commands at the prompt 709 // BUG, this needs to be disabled in menu mode 710 var completer = readline.NewPrefixCompleter( 711 readline.PcItem("help"), 712 readline.PcItem("address"), 713 readline.PcItem("balance"), 714 readline.PcItem("integrated_address"), 715 readline.PcItem("get_tx_key"), 716 readline.PcItem("menu"), 717 readline.PcItem("rescan_bc"), 718 readline.PcItem("rescan_spent"), 719 readline.PcItem("payment_id"), 720 readline.PcItem("print_height"), 721 readline.PcItem("seed"), 722 723 readline.PcItem("set", 724 readline.PcItem("mixin"), 725 readline.PcItem("seed"), 726 readline.PcItem("priority"), 727 ), 728 readline.PcItem("show_transfers"), 729 readline.PcItem("spendkey"), 730 readline.PcItem("status"), 731 readline.PcItem("viewkey"), 732 readline.PcItem("version"), 733 readline.PcItem("transfer"), 734 readline.PcItem("transfer_all"), 735 readline.PcItem("walletviewkey"), 736 readline.PcItem("bye"), 737 readline.PcItem("exit"), 738 readline.PcItem("quit"), 739 ) 740 741 // help command screen 742 func usage(w io.Writer) { 743 io.WriteString(w, "commands:\n") 744 io.WriteString(w, "\t\033[1mhelp\033[0m\t\tthis help\n") 745 io.WriteString(w, "\t\033[1maddress\033[0m\t\tDisplay user address\n") 746 io.WriteString(w, "\t\033[1mbalance\033[0m\t\tDisplay user balance\n") 747 io.WriteString(w, "\t\033[1mget_tx_key\033[0m\tDisplay tx secret key for specific transaction\n") 748 io.WriteString(w, "\t\033[1mintegrated_address\033[0m\tDisplay random integrated address (with encrypted payment ID)\n") 749 io.WriteString(w, "\t\033[1mmenu\033[0m\t\tEnable menu mode\n") 750 io.WriteString(w, "\t\033[1mrescan_bc\033[0m\tRescan blockchain again from 0 height\n") 751 io.WriteString(w, "\t\033[1mpassword\033[0m\tChange wallet password\n") 752 io.WriteString(w, "\t\033[1mpayment_id\033[0m\tPrint random Payment ID (for encrypted version see integrated_address)\n") 753 io.WriteString(w, "\t\033[1mseed\033[0m\t\tDisplay seed\n") 754 io.WriteString(w, "\t\033[1mshow_transfers\033[0m\tShow all transactions to/from current wallet\n") 755 io.WriteString(w, "\t\033[1mset\033[0m\t\tSet/get various settings\n") 756 io.WriteString(w, "\t\033[1mstatus\033[0m\t\tShow general information and balance\n") 757 io.WriteString(w, "\t\033[1mspendkey\033[0m\tView secret key\n") 758 io.WriteString(w, "\t\033[1mtransfer\033[0m\tTransfer/Send DERO to another address\n") 759 io.WriteString(w, "\t\t\tEg. transfer <address> <amount> [ <address2> <amount2> ]... [<payment_id>] \n") 760 io.WriteString(w, "\t\033[1mtransfer_all\033[0m\tTransfer everything to another address\n") 761 io.WriteString(w, "\t\033[1mviewkey\033[0m\t\tView view key\n") 762 io.WriteString(w, "\t\033[1mwalletviewkey\033[0m\tWallet view key, used to create watchable view only wallet\n") 763 io.WriteString(w, "\t\033[1mversion\033[0m\t\tShow version\n") 764 io.WriteString(w, "\t\033[1mbye\033[0m\t\tQuit wallet\n") 765 io.WriteString(w, "\t\033[1mexit\033[0m\t\tQuit wallet\n") 766 io.WriteString(w, "\t\033[1mquit\033[0m\t\tQuit wallet\n") 767 768 } 769 770 // display seed to the user in his preferred language 771 func display_seed(l *readline.Instance, wallet *walletapi.Wallet) { 772 seed := wallet.GetSeed() 773 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") 774 fmt.Fprintf(os.Stderr, color_red+"%s"+color_white+"\n", seed) 775 776 } 777 778 // display spend key 779 // viewable wallet do not have spend secret key 780 // TODO wee need to give user a warning if we are printing secret 781 func display_spend_key(l *readline.Instance, wallet *walletapi.Wallet) { 782 783 keys := wallet.Get_Keys() 784 if !account.ViewOnly { 785 fmt.Fprintf(os.Stderr, "spend key secret : "+color_red+"%s"+color_white+"\n", keys.Spendkey_Secret) 786 } 787 fmt.Fprintf(os.Stderr, "spend key public : %s\n", keys.Spendkey_Public) 788 } 789 790 //display view key 791 func display_view_key(l *readline.Instance, wallet *walletapi.Wallet) { 792 793 keys := wallet.Get_Keys() 794 fmt.Fprintf(os.Stderr, "view key secret : "+color_yellow+"%s"+color_white+"\n", keys.Viewkey_Secret) 795 fmt.Fprintf(os.Stderr, "view key public : %s\n", keys.Viewkey_Public) 796 797 } 798 799 // display wallet view only Keys 800 // this will create a watchable view only mode 801 func display_viewwallet_key(l *readline.Instance, wallet *walletapi.Wallet) { 802 803 io.WriteString(l.Stderr(), fmt.Sprintf("This Key can used to create a watch only wallet. This wallet can only see incoming funds but cannot spend them.\nThe key is below.\n%s\n\n", wallet.GetViewWalletKey())) 804 805 } 806 807 // start a rescan from block 0 808 func rescan_bc(wallet *walletapi.Wallet) { 809 if wallet.GetMode() { // trigger rescan we the wallet is online 810 wallet.Clean() // clean existing data from wallet 811 wallet.Rescan_From_Height(0) 812 } 813 814 } 815 816 // show the transfers to the user originating from this account 817 func show_transfers(l *readline.Instance, wallet *walletapi.Wallet, limit uint64) { 818 available := true 819 in := true 820 out := true 821 pool := true // this is not processed still TODO list 822 failed := false // this is not processed still TODO list 823 min_height := uint64(0) 824 max_height := uint64(0) 825 826 line := "" 827 line_parts := strings.Fields(line) 828 if len(line_parts) >= 2 { 829 switch strings.ToLower(line_parts[1]) { 830 case "available": 831 available = true 832 in = false 833 out = false 834 pool = false 835 failed = false 836 case "in": 837 available = true 838 in = true 839 out = false 840 pool = false 841 failed = false 842 case "out": 843 available = false 844 in = false 845 out = true 846 pool = false 847 failed = false 848 case "pool": 849 available = false 850 in = false 851 out = false 852 pool = true 853 failed = false 854 case "failed": 855 available = false 856 in = false 857 out = false 858 pool = false 859 failed = true 860 861 } 862 } 863 864 if len(line_parts) >= 3 { // user supplied min height 865 s, err := strconv.ParseUint(line_parts[2], 10, 64) 866 if err != nil { 867 globals.Logger.Warnf("Error parsing minimum height") 868 return 869 } 870 min_height = s 871 } 872 873 if len(line_parts) >= 4 { // user supplied max height 874 s, err := strconv.ParseUint(line_parts[2], 10, 64) 875 if err != nil { 876 globals.Logger.Warnf("Error parsing maximum height") 877 return 878 } 879 max_height = s 880 } 881 882 // request payments without payment id 883 transfers := wallet.Show_Transfers(available, in, out, pool, failed, false, min_height, max_height) // receives sorted list of transfers 884 885 if len(transfers) == 0 { 886 globals.Logger.Warnf("No transfers available") 887 return 888 } 889 // we need to paginate on say 20 transactions 890 891 paging := 20 892 893 /*if limit != 0 && uint64(len(transfers)) > limit { 894 transfers = transfers[uint64(len(transfers))-limit:] 895 }*/ 896 for i := range transfers { 897 898 switch transfers[i].Status { 899 case 0: 900 901 if len(transfers[i].PaymentID) == 0 { 902 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.FormatMoney12(transfers[i].Amount))) 903 } else { 904 payment_id := fmt.Sprintf("%x", transfers[i].PaymentID) 905 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.FormatMoney12(transfers[i].Amount), payment_id)) 906 } 907 908 case 1: 909 io.WriteString(l.Stderr(), fmt.Sprintf(color_magenta+"%s Height %d TopoHeight %d transaction %s spent %s DERO"+color_white+"\n",transfers[i].Time.Format(time.RFC822), transfers[i].Height, transfers[i].TopoHeight, transfers[i].TXID, globals.FormatMoney12(transfers[i].Amount))) 910 case 2: 911 fallthrough 912 default: 913 globals.Logger.Warnf("Transaction status unknown TXID %s status %d", transfers[i].TXID, transfers[i].Status) 914 915 } 916 917 if i != 0 && i%paging == 0 && (i+1) < len(transfers) { // ask user whether he want to see more till he quits 918 if !ConfirmYesNoDefaultNo(l, "Want to see more history (y/N)?") { 919 break // break loop 920 } 921 922 } 923 924 } 925 }