From c9979e4255b7e6e5919e220d66c3ae34c6874fa7 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sat, 22 Nov 2014 12:38:24 -0600 Subject: [PATCH] Initial commit --- README.md | 18 ++ __init__.py | 0 icon.ico | Bin 0 -> 370070 bytes lib/__init__.py | 0 lib/argparsetest.py | 7 + lib/balancecheck.py | 49 ++++ lib/bitmonerodloader.py | 16 ++ lib/checkbalance.py | 36 +++ lib/checklastblock.py | 43 +++ lib/checkwallet.py | 22 ++ lib/cpuminerloader.py | 16 ++ lib/daemoncheck.py | 46 ++++ lib/gettransfers.py | 29 ++ lib/isrunning.py | 58 ++++ lib/namecheck.py | 22 ++ lib/save_bc.py | 26 ++ lib/savewallet.py | 25 ++ lib/simplewalletloader.py | 62 +++++ lib/transferfunds.py | 64 +++++ lightWallet.kv | 332 ++++++++++++++++++++++ lightWallet.py | 560 ++++++++++++++++++++++++++++++++++++++ lilicon.ico | Bin 0 -> 16958 bytes logo.png | Bin 0 -> 48454 bytes 23 files changed, 1431 insertions(+) create mode 100644 README.md create mode 100644 __init__.py create mode 100644 icon.ico create mode 100644 lib/__init__.py create mode 100644 lib/argparsetest.py create mode 100644 lib/balancecheck.py create mode 100644 lib/bitmonerodloader.py create mode 100644 lib/checkbalance.py create mode 100644 lib/checklastblock.py create mode 100644 lib/checkwallet.py create mode 100644 lib/cpuminerloader.py create mode 100644 lib/daemoncheck.py create mode 100644 lib/gettransfers.py create mode 100644 lib/isrunning.py create mode 100644 lib/namecheck.py create mode 100644 lib/save_bc.py create mode 100644 lib/savewallet.py create mode 100644 lib/simplewalletloader.py create mode 100644 lib/transferfunds.py create mode 100644 lightWallet.kv create mode 100644 lightWallet.py create mode 100644 lilicon.ico create mode 100644 logo.png diff --git a/README.md b/README.md new file mode 100644 index 0000000..f71c009 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# lightWallet Kivy GUI +This is a GUI frontend that runs on top of bitmonerod and simplewallet (in RPC mode). It is designed to work with a remote bitmonerod node, so that it can function as a lightweight wallet for most users. **This program is an early beta version, and although I don't think there is really any way to screw up your wallet, please exercise some modicum of caution.** + +## Installation +### Windows, Linux, and Mac +The requirements for running the program are: Python 2.7, Kivy 1.8, and pygame 1.9.1. If you have these requirements, then you just need to add your binary (simplewallet.exe) and optionally wallet files to your cryptonoteRPCwalletGUI folder, or unzip the cryptonoteRPCwalletGUI contents into the folder where you currently have your binaries and wallet files. For the time being they have to be in the same local directory. + +Currently there are no binaries available, but the plan is to compile an exe for Windows, andfrom there it should be easy to create binaries for linux. + +## Running +It should be OK to launch the program with an instance of bitmonerod and/or simplewallet running. + +On the initial run, it will prompt you to create a new wallet or to import one by typing/copying in the path of the keys file. This will create a folder in the local directory where the new/imported wallet data is stored, and a file called CONFIG.file that let's lightWallet know which account is currently the default one being used. + +## Future Work +There are several features I'd like to implement, the most important of which is probably tab or file keeping a record of all incoming and outgoing transactions. This is a really key feature that should probably be a part of any wallet, but I'm still having some trouble with the gui here, so it is delayed for the time being. + +Another thing I want to change is using argparse rather than argv to parse command line arguments. This should make the program more robust (less crashy) and offer the user additional help at the command line. I probably want to clean up the interface and prettify it a bit more too. I'm open to suggestions too, so please open an issue or email me if you have any ideas, complaints, etc. \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1724aff352afcf0e1ba63381fa2937f427bcae4a GIT binary patch literal 370070 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 1: + payload = json.dumps({ + "jsonrpc":"2.0", + "method":"transfer", + "params":{ + "destinations":[ + { + "amount":atomicamount.value, + "address":address + } + ], + "fee":mro_fee.value, + "mixin":mixin, + "unlock_time":0, + "payment_id":paymentid + } + }) + else: + payload = json.dumps({ + "jsonrpc":"2.0", + "method":"transfer", + "params":{ + "destinations":[ + { + "amount":atomicamount.value, + "address":address + } + ], + "fee":mro_fee.value, + "mixin":mixin, + "unlock_time":0 + } + }) + print payload + + try: + headers = {'content-type': 'application/json'} + resp = requests.get(serverURL, headers=headers, data=payload) + output = json.loads(resp.text) + print output + txid = output[u'result'][u'tx_hash'] + return txid + + except: + print "transfer error :0 !!!" + return "error" + + +if __name__ == "__main__": + transferfundsrpccall() \ No newline at end of file diff --git a/lightWallet.kv b/lightWallet.kv new file mode 100644 index 0000000..a82e935 --- /dev/null +++ b/lightWallet.kv @@ -0,0 +1,332 @@ +#:kivy 1.8.0 + +: + id: rootwidget + # Tx history tab id items + tx_list: tx_list + # Transfer tab id items + address_input_textinput: address_input_textinput + amount_input_textinput: amount_input_textinput + mixin_input_textinput: mixin_input_textinput + paymentid_input_textinput: paymentid_input_textinput + # Wallet account tab id items + wallet_account_label: wallet_account_label + wallet_address_label: wallet_address_label + wallet_block_label: wallet_block_label + unlocked_balance_label: unlocked_balance_label + total_balance_label: total_balance_label + calculated_balance_label: calculated_balance_label + # Bitmonerod daemon tab id items + daemon_server_textinput: daemon_server_textinput + blockheight_label: blockheight_label + last_reward_label: last_reward_label + last_block_time_label: last_block_time_label + time_since_last_label: time_since_last_label + + AccordionItem: + title: "Transaction history" + ScrollView: + size_hint: None, None + GridLayout: + cols: 1 + spacing: 7 + # size_hint_y: None + id: tx_list + + AccordionItem: + title: "Send XMR" + BoxLayout: + padding: 30, 30, 30, 30 + orientation: 'vertical' + Label: + text: "Send to address:" + size_hint_x: 0.2 + TextInput: + id: address_input_textinput + size_hint_x: 2 + font_size: 10 + multiline: False + Label: + text: "Amount MRO:" + size_hint_x: 0.2 + TextInput: + id: amount_input_textinput + size_hint_x: 0.8 + font_size: 10 + multiline: False + Label: + text: "Mixin amount:\n(Please use 0 or >= 3)" + size_hint_x: 0.2 + TextInput: + id: mixin_input_textinput + size_hint_x: 0.8 + font_size: 10 + multiline: False + Label: + text: "Payment ID:" + size_hint_x: 0.2 + TextInput: + id: paymentid_input_textinput + size_hint_x: 1 + font_size: 10 + text: "" + multiline: False + Button: + text: "Transfer funds" + on_press: root.transferfunds() + # on_press: root.storeformdata() + font_size: 20 + + AccordionItem: + title: "Simplewallet account" + BoxLayout: + orientation: 'vertical' + padding: 30, 30, 30, 30 + BoxLayout: + orientation: 'horizontal' + Label: + text: "Account name: " + MarkupLabel: + id: wallet_account_label + BoxLayout: + orientation: 'horizontal' + Label: + size_hint_x: 0.15 + text: "Address: " + MarkupLabel: + id: wallet_address_label + size_hint_x: 0.9 + font_size: 9 + on_ref_press: root.selectAddress() + BoxLayout: + orientation: 'horizontal' + Label: + text: "Blocks synced: " + MarkupLabel: + id: wallet_block_label + BoxLayout: + orientation: 'horizontal' + Label: + text: "Unlocked balance: " + MarkupLabel: + id: unlocked_balance_label + BoxLayout: + orientation: 'horizontal' + Label: + text: "Total balance: " + MarkupLabel: + id: total_balance_label + BoxLayout: + orientation: 'horizontal' + Label: + text: "Calculated balance: " + MarkupLabel: + id: calculated_balance_label + + AccordionItem: + title: "Bitmonerod daemon" + BoxLayout: + orientation: 'vertical' + padding: 30, 30, 30, 30 + Image: + size_hint_y: 0.2 + source: 'logo.png' + Label: + size_hint_y: 0.05 + text: "A lightweight Monero GUI account manager" + Label: + size_hint_y: 0.1 + Label: + text: "Daemon ip address (default is coolmining club)" + size_hint_y: 0.05 + BoxLayout: + size_hint_y: 0.15 + orientation: 'horizontal' + TextInput: + size_hint_x: 0.7 + id: daemon_server_textinput + text: "http://xmr1.coolmining.club:5012" + # text: "http://localhost:18081" + Button: + text: "Click to change\ndaemon address" + Label: + size_hint_y: 0.1 + BoxLayout: + size_hint_y: 0.05 + orientation: 'horizontal' + Label: + text: 'Blockchain height:' + MarkupLabel: + id: blockheight_label + BoxLayout: + size_hint_y: 0.05 + orientation: 'horizontal' + Label: + text: 'Last block reward:' + MarkupLabel: + id: last_reward_label + BoxLayout: + size_hint_y: 0.05 + orientation: 'horizontal' + Label: + text: 'Last block time:' + MarkupLabel: + id: last_block_time_label + BoxLayout: + size_hint_y: 0.05 + orientation: 'horizontal' + Label: + text: 'Time since last block:' + MarkupLabel: + id: time_since_last_label + + +: + id: initial_popup + title: "Initial Setup Window" + auto_dismiss: "False" + BoxLayout: + orientation: 'vertical' + Label: + text: "It appears that this is your first time loading CryptoNote Kivy GUI\n(or you have deleted your CONFIG.file file).\nPlease select if you would like to create a new wallet, load an existing wallet, or exit the program" + BoxLayout: + orientation: 'horizontal' + padding: 30, 20, 30, 20 + Button: + text: "Create\nNew\nWallet" + on_press: root.createWalletPopup() + Button: + text: "Load\nExisting\nWallet" + on_press: root.loadWalletPopup() + Button: + text: "Exit KivyWallet" + on_press: root.quit() + + +: + id: create_wallet_popup + title: "Create Wallet Window" + auto_dismiss: "False" + wallet_name: wallet_name + wallet_pw: wallet_pw + repeat_wallet_pw: repeat_wallet_pw + BoxLayout: + padding: 60, 60, 60, 60 + orientation: 'vertical' + Label: + text: "Enter wallet name:" + TabTextInput: + id: wallet_name + next: wallet_pw + Label: + text: "Enter wallet password:" + TabTextInput: + id: wallet_pw + password: True + next: repeat_wallet_pw + Label: + text: "Repeat wallet password:" + TabTextInput: + id: repeat_wallet_pw + password: True + next: wallet_pw + Button: + text: "Click to create wallet" + on_press: root.createWallet() + + +: + id: load_wallet_popup + title: "Load Wallet Window" + auto_dismiss: "False" + wallet_path: wallet_path + wallet_pw: wallet_pw + repeat_wallet_pw: repeat_wallet_pw + BoxLayout: + padding: 60, 60, 60, 60 + orientation: 'vertical' + MarkupLabel: + text: "Enter wallet keys path (make sure you include file extension)\nExample - [b][color=00ffff]C:\\Users\\Satoshi\\SecretStuff\\Wallets\\Monero\\wallet.bin.keys[/b][/color]:" + TabTextInput: + id: wallet_path + next: wallet_pw + Label: + text: "Enter wallet password:" + TabTextInput: + id: wallet_pw + password: True + next: repeat_wallet_pw + Label: + text: "Repeat wallet password:" + TabTextInput: + id: repeat_wallet_pw + password: True + next: wallet_path + Label: + text: "Note - this wallet will be set as your default wallet" + Button: + text: "Click to load wallet" + on_press: root.loadWallet() + + + + title: "Password entry popup" + walletname_label: walletname_label + wallet_pw: wallet_pw + BoxLayout: + orientation: "vertical" + Label: + id: walletname_label + TabTextInput: + id: wallet_pw + password: True + Button: + text: "Click to launch wallet" + on_press: root.launchWallet() + + + + transfer_popup_label: transfer_popup_label + size_hint: 0.75, 0.75 + auto_dismiss: False + title: "Transfer Popup" + BoxLayout: + orientation: 'vertical' + Label: + # text: "are you sure?" + id: transfer_popup_label + text: "" + markup: True + BoxLayout: + orientation: 'horizontal' + Button: + text: "Yes, transfer XMR." + on_press: root.transfer() + on_press: root.dismiss() + Button: + text: "No, cancel transaction." + on_press: root.dismiss() + + + + title: "Tx ID Popup" + txid_popup_label: txid_popup_label + BoxLayout: + orientation: 'vertical' + Label: + font_size: 11 + markup: True + id: txid_label + text: "" + on_ref_press: root.selecttxid() + Button: + text: "OK, dismiss." + on_press: root.dismiss() + + + + markup: True + + + + multiline: False \ No newline at end of file diff --git a/lightWallet.py b/lightWallet.py new file mode 100644 index 0000000..c52ea09 --- /dev/null +++ b/lightWallet.py @@ -0,0 +1,560 @@ +# !/usr/bin/python +from platform import system +from kivy.app import App +from kivy.config import Config +from kivy.uix.accordion import Accordion +from kivy.uix.button import Button +from kivy.uix.label import Label +from kivy.properties import ObjectProperty +from kivy.core.clipboard import Clipboard +from kivy.clock import Clock +from kivy.uix.popup import Popup +from kivy.storage.jsonstore import JsonStore +from kivy.uix.textinput import TextInput + +import os +import sys +import argparse +import time +from subprocess import PIPE, Popen, STDOUT +from threading import Thread +from Queue import Queue, Empty +import shutil + +from lib.checklastblock import CheckLastBlock +from lib.savewallet import storeWallet +from lib.transferfunds import transferfundsrpccall + + +# Set POSIX argument for linux for something... +ON_POSIX = 'posix' in sys.builtin_module_names + + +# parser gets command line args +parser = argparse.ArgumentParser() +parser.parse_args() + + +# define functions to read output streams +def enqueue_output(out, queue): + for line in iter(out.readline, b''): + queue.put(line) + out.close() + + +def read_output(process, stdout, queue): + while True: + out = stdout.readline() + #print("Process is: "+process.poll()) + if out == '' and process.poll() != None: + break + if out != '': + line = out.rstrip() + queue.put_nowait(line) + sys.stdout.flush() + + +# Function to split tx output lines +def txSplit(line): + split_line = line.split(': ') + try: + amount = float(split_line[1].split(',')[0]) + tx = split_line[2].strip('<>') + date = split_line[0].split('.')[0] + return amount, tx, date + except ValueError: + return "Error", "Error", "Error" + except IndexError: + return "Error", "Error", "Error" + + +class MarkupLabel(Label): + """ Label class with markup on by default """ + pass + + +class TabTextInput(TextInput): + """ Tab or enter to next TextInput class """ + + def __init__(self, *args, **kwargs): + self.next = kwargs.pop('next', None) + super(TabTextInput, self).__init__(*args, **kwargs) + + def set_next(self, next): + self.next = next + + def _keyboard_on_key_down(self, window, keycode, text, modifiers): + key, key_str = keycode + if key in (9, 13) and self.next is not None: + self.next.focus = True + self.next.select_all() + else: + super(TabTextInput, self)._keyboard_on_key_down(window, keycode, text, modifiers) + + +class InitialPopup(Popup): + """ Initial popup widget """ + + def loadWalletPopup(self): + self.dismiss() + lwp = LoadWalletPopup() + lwp.open() + + def createWalletPopup(self): + self.dismiss() + cwp = CreateWalletPopup() + cwp.open() + + def quit(self): + sys.exit(0) + + +class CreateWalletPopup(Popup): + """ Wallet creation screen popup """ + wallet_name = ObjectProperty() + wallet_pw = ObjectProperty() + repeat_wallet_pw = ObjectProperty() + + def createWallet(self): + electrum_coming = False + wallet_name = self.wallet_name.text + wallet_pw = self.wallet_pw.text + if self.wallet_pw.text == self.repeat_wallet_pw.text: + p = Popen(["simplewallet", "--generate-new-wallet", + "./" + wallet_name.replace('.', '') + "_walletData/" + wallet_name, + "--password", wallet_pw], + stdout=PIPE, + stdin=PIPE, + bufsize=1, + close_fds=ON_POSIX) + if not os.path.exists('./{0}_walletData'.format(wallet_name.replace('.', ''))): + os.mkdir('./{0}_walletData'.format(wallet_name.replace('.', ''))) + else: + sys.exit(7777) + q = Queue() + t = Thread(target=enqueue_output, args=(p.stdout, q)) + t.daemon = True # thread dies with the program + t.start() + t_start = time.time() + while time.time() - t_start < 2: + try: + line = q.get_nowait() # or q.get(timeout=.1) + if "new wallet:" in line: + address = line.rstrip().split(': ')[1] + if "view key:" in line: + view_key = line.rstrip().split(': ')[1] + if 130 < len(line.rstrip()) < 350: + electrum = line.rstrip() + if "PLEASE NOTE:" in line: + electrum_coming = line.rstrip() + print("simplewallet output:", line.rstrip()) + except Empty: + # print('no output yet') + pass + else: # got line + # print(line) + pass + # time.sleep(0.1) + p.kill() + # os.system("taskkill /im simplewallet.exe /f") + # print(p.pid) + with open('./{0}_walletData/info.txt'.format(wallet_name.replace('.', '')), 'w') as f: + f.write("Name:\n{0}\n\nAddress:\n{1}\n\nView key:\n{2}\n\nElectrum seed:\n{3}".format( + wallet_name, address, view_key, electrum)) + with open("./CONFIG.file", 'w') as f: + f.write(wallet_name) + self.dismiss() + content = Button( + text="Your wallet has been created in the local directory {0}_walletData\nThe info.txt document in that folder contains your wallet recovery seed.\nPlease keep this seed safe, you can restore a corrupted/lost wallet with it.\nSomeone else can also bypass your password and steal your XMR with it.\nI suggest deleting or encrypting this file.".format( + wallet_name)) + walletCreatedPopup = Popup(title="Wallet created window", + content=content) + content.bind(on_press=walletCreatedPopup.dismiss) + walletCreatedPopup.open() + App.get_running_app().root.launchWallet(wallet_name, wallet_pw) + else: + content = Button(text="Check passwords!!!\nClick to dismiss") + pwErrorPopup = Popup(title="Password match error window", + content=content) + content.bind(on_press=pwErrorPopup.dismiss) + pwErrorPopup.open() + + +class LoadWalletPopup(Popup): + """ Wallet loading screen popup """ + wallet_path = ObjectProperty() + wallet_pw = ObjectProperty() + repeat_wallet_pw = ObjectProperty() + + def loadWallet(self): + split_name = os.path.split(self.wallet_path.text)[1].split('.') + wallet_name = '.'.join(tuple(split_name[0:len(split_name) - 1])) + print(split_name, wallet_name) + wallet_pw = self.wallet_pw.text + wallet_error = False + if self.wallet_pw.text == self.repeat_wallet_pw.text: + p = Popen(["simplewallet", "--wallet-file", + self.wallet_path.text, + "--password", wallet_pw], + stdout=PIPE, + stdin=PIPE, + bufsize=1, + close_fds=ON_POSIX) + q = Queue() + t = Thread(target=enqueue_output, args=(p.stdout, q)) + t.daemon = True # thread dies with the program + t.start() + t_start = time.time() + while time.time() - t_start < 2: + try: + line = q.get_nowait() # or q.get(timeout=.1) + print("simplewallet output:", line.rstrip()) + if "Opened wallet: " in line.rstrip(): + address = line.rstrip().split(": ")[1] + if "Error: failed" in line.rstrip(): + wallet_error = True + except Empty: + # print('no output yet') + pass + else: # got line + pass + p.kill() + if not wallet_error: + if not os.path.exists("./{0}_walletData".format(wallet_name.replace('.', ''))): + os.mkdir("./{0}_walletData".format(wallet_name.replace('.', ''))) + with open("./CONFIG.file", 'w') as f: + f.write(wallet_name) + with open("./{0}_walletData/{1}.address.txt".format(wallet_name.replace('.', ''), wallet_name), 'w') as f: + f.write(address) + shutil.copyfile(self.wallet_path.text, + "./{0}_walletData/{1}.keys".format(wallet_name.replace('.', ''), wallet_name)) + self.dismiss() + content = Button( + text="Your wallet keys file has been copied to the local directory {0}_walletData\nThe original has not been disturbed.".format( + wallet_name)) + walletCopiedPopup = Popup(title="Wallet keys copied window", + content=content) + content.bind(on_press=walletCopiedPopup.dismiss) + walletCopiedPopup.open() + App.get_running_app().root.launchWallet(wallet_name, wallet_pw) + else: + content = Button(text="Wallet import error!\nClick to start over") + pwErrorPopup = Popup(title="Wallet import error window", + content=content) + content.bind(on_press=pwErrorPopup.dismiss) + ip = InitialPopup() + content.bind(on_press=ip.open) + pwErrorPopup.open() + + +class PasswordPopup(Popup): + """ Popup class to get pw on launch with CONFIG.file """ + wallet_pw = ObjectProperty() + walletname_label = ObjectProperty() + + def __init__(self, wallet_name): + super(PasswordPopup, self).__init__() + self.wallet_name = wallet_name + if not App.get_running_app().root.wallet_error: + self.walletname_label.text = "Please enter your password for {0} account:".format(self.wallet_name) + if App.get_running_app().root.wallet_error: + self.walletname_label.text = "There seems to be a problem with binary file,\nPlease enter your password to attempt rebuild of {0} account:".format( + self.wallet_name) + + def launchWallet(self): + wallet_pw = self.wallet_pw.text + App.get_running_app().root.launchWallet(self.wallet_name, wallet_pw) + self.dismiss() + + +class TransferPopup(Popup): + """ Popup class to initiate XMR transfer """ + transfer_popup_label = ObjectProperty() + + def __init__(self, amount, address, mixin, paymentid): + super(TransferPopup, self).__init__() + self.amount = amount + self.address = address + self.mixin = mixin + self.paymentid = paymentid + self.popuptext = "Are you sure you want to send {0:0.9f} XMR\n to [size=8]{1}[/size]\n with mixin count {2} and payment id [size=8]{3}[/size]".format( + float(amount), address, mixin, paymentid) + self.transfer_popup_label.text = self.popuptext + + def transfer(self): + txid = transferfundsrpccall(self.amount, self.address, self.mixin, self.paymentid) + txid_popup = TxIDPopup(txid) + txid_popup.open() + + +class TxIDPopup(Popup): + txid_label = ObjectProperty() + def __init__(self, txid): + super(TxIDPopup, self).__init__() + self.txid = txid + self.txid_label.text = "TxID: [ref=txid]{0}[/ref]\n(Click TxID to copy to clipboard)".format(txid) + + def selecttxid(self): + Clipboard.put(self.txid) + + +class RootWidget(Accordion): + """Root Kivy accordion widget class""" + wallet_error = None + # Transfer XMR id items + address_input_textinput = ObjectProperty() + amount_input_textinput = ObjectProperty() + mixin_input_textinput = ObjectProperty() + paymentid_input_textinput = ObjectProperty() + # Wallet account id items + wallet_account_label = ObjectProperty() + wallet_address_label = ObjectProperty() + unlocked_balance_label = ObjectProperty() + total_balance_label = ObjectProperty() + calculated_balance_label = ObjectProperty() + # Daemon id items + daemon_server_textinput = ObjectProperty() + blockheight_label = ObjectProperty() + last_reward_label = ObjectProperty() + last_block_time_label = ObjectProperty() + time_since_last_label = ObjectProperty() + wallet_block_label = ObjectProperty() + blockheight = 0 + # Process, queue, and thread variables + daemon_queue = Queue() + daemon_thread = None + wallet_process = None + wallet_queue = Queue() + wallet_thread = None + # Other variables + calculated_balance = 0.0 + total_balance = 0.0 + unlocked_balance = 0.0 + temp_tx_amount = 0.0 + temp_tx_hash = "" + temp_tx_paymentid = "" + wallet_save_time = None + + def __init__(self, **kwargs): + super(RootWidget, self).__init__(**kwargs) + self.daemon_thread = Thread(target=self.checkDaemonStatus, + args=(self.daemon_queue, self.daemon_server_textinput.text)) + self.daemon_thread.daemon = True + self.daemon_thread.start() + if os.path.isfile('./CONFIG.file'): + with open('./CONFIG.file', 'r') as f: + self.wallet_name = f.read().rstrip() + print(self.wallet_name) + Clock.schedule_once(self.initialPassword, 0.5) + else: + Clock.schedule_once(self.initialConfig, 0.5) + Clock.schedule_interval(self.checkDaemon, 5) + + + def initialConfig(self, dt): + ip = InitialPopup() + ip.open() + + def initialPassword(self, dt): + gp = PasswordPopup(self.wallet_name) + gp.open() + + def checkDaemonStatus(self, daemon_queue, server): + """Class to be threaded for checking bitmonerod status""" + # print(server) + height, reward, timesince, localtime = CheckLastBlock(server + "/json_rpc") + daemon_queue.put((height, reward, timesince, localtime)) + # print("sleeping") + time.sleep(1) + self.checkDaemonStatus(daemon_queue, self.daemon_server_textinput.text) + + def checkDaemon(self, dt): + try: + height, reward, timesince, localtime = self.daemon_queue.get_nowait() + self.blockheight = int(height) + self.blockheight_label.text = "[b][color=00ffff]{0}[/b][/color]".format(height) + self.last_reward_label.text = "[b][color=00ffff]{0:.2f}[/b][/color] XMR".format(reward * 1e-12) + self.last_block_time_label.text = "[b][color=00ffff]{0}[/b][/color]".format(localtime) + self.time_since_last_label.text = "[b][color=00ffff]{0:.0f}[/b][/color] seconds".format(timesince) + except: + pass + + def launchWallet(self, wallet_name, wallet_pw): + with open("./{0}_walletData/{1}.address.txt".format(wallet_name.replace('.', ''), wallet_name), 'r') as f: + self.address = f.read().rstrip() + self.wallet_account_label.text = "[b][color=00ffff]{0}[/b][/color]".format(wallet_name) + self.wallet_address_label.text = "[b][color=00ffff][ref=Address]{0}[/ref][/b][/color]".format(self.address) + if not os.path.isfile("./{0}_walletData/{1}".format(wallet_name.replace('.', ''), wallet_name)): + self.wallet_process = Popen(["simplewallet.exe", "--wallet-file", "./{0}_walletData/{1}".format( + wallet_name.replace('.', ''), wallet_name), "--password", wallet_pw], + stdin=PIPE, + stdout=PIPE, + stderr=STDOUT, + bufsize=1) + self.wallet_process.communicate(input="exit\r\n") + self.wallet_process.kill() + # try: + # system("taskkill /im simplewallet.exe /f") + # except: + # print("Simplewallet.exe not running") + if os.path.isfile("./{0}_walletData/tx.txt".format(wallet_name.replace('.', ''))): + with open("./{0}_walletData/tx.txt".format(wallet_name.replace('.', ''), 'r')) as previous_txs: + txs = previous_txs.read().split('\n') + for idx, line in enumerate(txs): + this_amount, this_tx, this_date = txSplit(line) + if idx < len(txs) - 1: + next_amount, next_tx, date = txSplit(txs[idx+1]) + else: + next_amount, next_tx, date = "Done", "Done", "Done" + if this_tx != next_tx and self.temp_tx_amount == 0.0: + if "Received" in line: + l = Label(text="Received {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx), + size_hint_y=None, height=15, font_size=9) + self.ids.tx_list.add_widget(l) + self.calculated_balance += this_amount + elif "Spent" in line: + l = Label(text="Spent {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx), + size_hint_y=None, height=15, font_size=9) + self.ids.tx_list.add_widget(l) + self.calculated_balance -= this_amount + elif this_tx == next_tx and self.temp_tx_amount == 0.0: + self.temp_tx_amount += this_amount + next_amount + elif this_tx == next_tx and self.temp_tx_amount != 0.0: + self.temp_tx_amount += next_amount + elif this_tx != next_tx and self.temp_tx_amount != 0.0: + if "Received" in line: + l = Label(text="Received {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx), + size_hint_y=None, height=15, font_size=9) + self.ids.tx_list.add_widget(l) + self.calculated_balance += self.temp_tx_amount + elif "Spent" in line: + l = Label(text="Spent {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx), + size_hint_y=None, height=15, font_size=9) + self.ids.tx_list.add_widget(l) + self.calculated_balance -= self.temp_tx_amount + self.temp_tx_amount = 0.0 + self.temp_tx_amount = 0.0 + self.calculated_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color]".format(self.calculated_balance) + self.wallet_process = Popen(["simplewallet", "--wallet-file", "./{0}_walletData/{1}.keys".format( + wallet_name.replace('.', ''), wallet_name), "--password", wallet_pw, + "--daemon-address", self.daemon_server_textinput.text, "--rpc-bind-port", "19091"], + stdin=PIPE, + stdout=PIPE, + stderr=STDOUT, + bufsize=1) + self.wallet_thread = Thread(target=read_output, + args=(self.wallet_process, self.wallet_process.stdout, self.wallet_queue)) + # self.wallet_thread = Thread(target=enqueue_output, + # args=(self.wallet_process.stdout, self.wallet_queue)) + self.wallet_thread.daemon = True # thread dies with the program + self.wallet_thread.start() + Clock.schedule_interval(self.readWalletQueue, .01) + # Clock.schedule_interval(self.saveWallet, 60) + + def readWalletQueue(self, dt): + print(self.wallet_thread.is_alive) + try: + print("trying") + newLine = self.wallet_queue.get_nowait() + # with open("rpcwallet.log", 'a') as logfile: + # logfile.write(newLine+'\n') + print newLine + if "Wallet initialize failed: failed to read file" in newLine: + self.wallet_thread.join() + self.wallet_error = True + os.remove("./{0}_walletData/{1}".format(self.wallet_name.replace('.', ''), self.wallet_name)) + Clock.schedule_once(self.initialPassword, 0.5) + if "height:" in newLine: + height = newLine.split(", ")[1].split(": ")[1] + self.wallet_block_label.text = "[b][color=00ffff]{0}[/b][/color] of [b][color=00ffff]{1}[/b][/color]".format( + height, self.blockheight) + elif "height " in newLine: + height = newLine.split(", ")[1].split(" ")[1] + self.wallet_block_label.text = "[b][color=00ffff]{0}[/b][/color] of [b][color=00ffff]{1}[/b][/color]".format( + height, self.blockheight) + elif "Refresh done," in newLine: + print("refresh done") + split_line = newLine.split(',') + self.total_balance = float(split_line[2].split(': ')[1]) + self.unlocked_balance = float(split_line[3].split(': ')[1]) + self.total_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color]".format(self.total_balance) + self.unlocked_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color]".format(self.unlocked_balance) + if not self.wallet_save_time: + print("First save launching!") + Clock.schedule_once(self.saveWallet, 0.1) + self.wallet_save_time = time.time() + print("Wallet saved at: {0}".format(self.wallet_save_time)) + if self.wallet_save_time: + if time.time() - self.wallet_save_time > 1800: + Clock.schedule_once(self.saveWallet, 0.1) + print(time.time(), self.wallet_save_time, "saving") + self.wallet_save_time = time.time() + elif "Received money" in newLine and "with tx" in newLine: + with open("./{0}_walletData/tx.txt".format(self.wallet_name), 'a') as tx_file: + this_amount, this_tx, this_date = txSplit(newLine) + tx_file.write(newLine + '\n') + self.calculated_balance += this_amount + self.calculated_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color]".format( + self.calculated_balance) + l = Label(text="Received {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx), + size_hint_y=None, height=15, font_size=9) + self.ids.tx_list.add_widget(l) + elif "Spent money" in newLine and "with tx" in newLine: + with open("./{0}_walletData/tx.txt".format(self.wallet_name), 'a') as tx_file: + this_amount, this_tx, this_date = txSplit(newLine) + tx_file.write(newLine + '\n') + self.calculated_balance -= this_amount + l = Label(text="Spent {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx), + size_hint_y=None, height=15, font_size=9) + self.ids.tx_list.add_widget(l) + # elif "Payment found" in newLine: + # with open("./{0}_walletData/tx.txt".format(self.wallet_name), 'a') as tx_file: + # tx_file.write(newLine + '\n') + except Empty: + pass + # if not self.wallet_thread.is_alive(): + # print("dead") + # self.wallet_thread.start() + # Clock.schedule_interval(self.readWalletQueue, 0.01) + + def saveWallet(self, dt): + print("Launching save thread") + save_thread = Thread(target=storeWallet) + save_thread.daemon = True + save_thread.start() + save_thread.join() + print("Finished save thread") + + def selectAddress(self): + """copy address to clipboard when user clicks""" + print('User clicked on ', self.address) + self.daemon_server_textinput.copy(self.address) + + def transferfunds(self): + """initiate transfer of funds to new address""" + try: + float(self.amount_input_textinput.text) + tp = TransferPopup(self.amount_input_textinput.text, self.address_input_textinput.text, + self.mixin_input_textinput.text, self.paymentid_input_textinput.text) + tp.open() + except ValueError: + print("no,no") + + +class KVNewApp(App): + def build(self): + return RootWidget() + + +if __name__ == '__main__': + Config.set('kivy', 'exit_on_escape', 0) + Config.set('kivy', 'window_icon', "./lilicon.ico") + KVNewApp().run() + try: + App.get_running_app().root.wallet_process.kill() + except: + print("Wallet process not running") + # try: + # system("taskkill /im simplewallet.exe /f") + # except: + # print("Simplewallet.exe not running") diff --git a/lilicon.ico b/lilicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..61fb8d61b3a2d11f58d1acf1450f7c2eeb97c756 GIT binary patch literal 16958 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