From 6e80f4a560492f51beffbdc059fc8a7f748adaaa Mon Sep 17 00:00:00 2001 From: jwinterm Date: Mon, 16 Jun 2014 19:08:40 -0500 Subject: [PATCH] Major update to cryptonoteRPCwalletGUI --- README.md | 18 ++ functions/bitmonerodloader.py | 15 -- functions/checklastblock.py | 42 --- functions/simplewalletloader.py | 43 --- functions/transferfunds.py | 42 --- kivywallet.kv | 406 +++++++++++++++++------------ kivywallet.py | 285 +++++++++++++++++--- {functions => lib}/__init__.py | 0 lib/balancecheck.py | 49 ++++ lib/bitmonerodloader.py | 16 ++ {functions => lib}/checkbalance.py | 0 lib/checklastblock.py | 55 ++++ {functions => lib}/checkwallet.py | 0 lib/cpuminerloader.py | 16 ++ lib/daemoncheck.py | 46 ++++ 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 +++++ 21 files changed, 954 insertions(+), 336 deletions(-) delete mode 100644 functions/bitmonerodloader.py delete mode 100644 functions/checklastblock.py delete mode 100644 functions/simplewalletloader.py delete mode 100644 functions/transferfunds.py rename {functions => lib}/__init__.py (100%) create mode 100644 lib/balancecheck.py create mode 100644 lib/bitmonerodloader.py rename {functions => lib}/checkbalance.py (100%) create mode 100644 lib/checklastblock.py rename {functions => lib}/checkwallet.py (100%) create mode 100644 lib/cpuminerloader.py create mode 100644 lib/daemoncheck.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 diff --git a/README.md b/README.md index e69de29..b58fc57 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,18 @@ +# Cryptonote RPC wallet Kivy GUI +This is a GUI frontend that runs on top of bitmonerod, simplewallet (in RPC mode), and optionally LucasJones or Wolf's cryptonight cpuminer. **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 (or 2.6 may work), Kivy 1.8 (at least 1.4), and pygame 1.9.1 (others may work). If you have these requirements, then you just need to either add your binaries (bitmonerod(.exe), simplewallet(.exe), and optionally minerd(.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. I'm in the process of trying to compile an exe for Windows, and I think it should be easier to create binaries for linux. I've tested the program on Windows 7 & 8.1 and Debian Wheezy, and it seems to work OK on both. I think it should also work on Mac, but I haven't been able to test that yet. + +## Running +It should be OK to launch the program with an instance of bitmonerod, simplewallet, or minerd running, but the program won't allow you to launch another instance of these programs if it detects their already running. So, unless you know that you are running bitmonerod and simplewallet in RPC mode, it's probably best to just launch them from within the GUI program. If you close just the GUI though, it should be fine to relaunch when one or all of the binaries are already running. + +You can pass your wallet filename and password as arguments on the commandline, if you're launching from the command line, however this is currently not done in the most robust way possible, so make sure you just pass two arguments, the first being wallet name and second password, or the program will probably fail to start. I'm looking to make this more robust using argparse rather than argv. + +## 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 incoming transactions, 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/functions/bitmonerodloader.py b/functions/bitmonerodloader.py deleted file mode 100644 index 53fe8cd..0000000 --- a/functions/bitmonerodloader.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -import subprocess - -def bitmonerodloader(): - """Start bitmonerod and/or simplewallet and collect wallet name and address""" - #Start bitmonerod daemon if not running - if os.name == 'nt': - daemonproc = subprocess.Popen("start bitmonerod --rpc-bind-port 18081", - shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - elif os.name == 'posix': - daemonproc = subprocess.Popen(['./bitmonerod --rpc-bind-port 18081'], - creationflags=subprocess.CREATE_NEW_CONSOLE, - shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) diff --git a/functions/checklastblock.py b/functions/checklastblock.py deleted file mode 100644 index e7b61db..0000000 --- a/functions/checklastblock.py +++ /dev/null @@ -1,42 +0,0 @@ -import requests -import json -import time - -serverURL = 'http://localhost:8081/json_rpc' - -headers = {'content-type': 'application/json'} - -payload = json.dumps({ - "jsonrpc": "2.0", - "id": "test", - "method": "getlastblockheader" -}) - - -def checklastblockrpccall(): - synced = False - while synced == False: - resp = requests.get(serverURL, headers=headers, data=payload) - output = json.loads(resp.text) - try: - output[u'result'][u'status'] == 'OK' - synced = True - except: - time.sleep(2) - print "Waiting for bitmonerod client to sync with network..." - - try: - if output[u'error'][u'message'] == 'Core is busy': - time.sleep(3) - except: - print '2nd' - - - print "Client is synced with the network." - print output - time.sleep(4) - - return output - -if __name__ == "__main__": - checklastblockrpccall() \ No newline at end of file diff --git a/functions/simplewalletloader.py b/functions/simplewalletloader.py deleted file mode 100644 index 4236dff..0000000 --- a/functions/simplewalletloader.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -import time -import subprocess - -import checklastblock - -def simplewalletloader(walletname, walletpw, queue): - """Function to create and/or open wallet in simplewallet rpc mode""" - - #Generate wallet if file doesn't exist in local directory - if not os.path.isfile('./'+walletname): - print "Generating wallet file " + walletname - if os.name == 'nt': - walletgen = subprocess.Popen( - "simplewallet --generate-new-wallet " + walletname + " --password " + walletpw, - shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - elif os.name == 'posix': - walletgen = subprocess.Popen( - "./simplewallet --generate-new-wallet " + walletname + " --password " + walletpw, - shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - creationflags=subprocess.CREATE_NEW_CONSOLE) - - time.sleep(7) - walletgen.terminate() - - #Launch simplewallet in rpc mode - print "***Opening ___" + walletname + "___ wallet file rpc interface***" - if os.name == 'nt': - walletproc = subprocess.Popen("start simplewallet --wallet {0} --password {1} --rpc-bind-port 8082".format(walletname, walletpw), - shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - elif os.name == 'posix': - walletproc = subprocess.Popen(["./simplewallet --wallet {0} --password {1} --rpc-bind-port 8082".format( - walletname, walletpw)], creationflags=subprocess.CREATE_NEW_CONSOLE) - - #Read address file to get address - with open(walletname + ".address.txt", "r") as addressfile: - rawaddress = addressfile.read().replace('\n', '') - - #Format data for display in kivy - walletname = walletname + ' wallet file has been opened' - address = '[color=00ffff][ref=addressselection]' + rawaddress + '[/ref][/color]' - - queue.put((walletname, address, rawaddress)) \ No newline at end of file diff --git a/functions/transferfunds.py b/functions/transferfunds.py deleted file mode 100644 index 05bde1a..0000000 --- a/functions/transferfunds.py +++ /dev/null @@ -1,42 +0,0 @@ -import requests -import json -from ctypes import c_uint64 - -serverURL = 'http://localhost:8082/json_rpc' - - -def transferfundsrpccall(amount, address, mixin): - """function to transfer funds to a single address""" - atomicamount = c_uint64(int((float(amount)*1e12)-1e6)) - address = str(address) - mixin = int(mixin) - mro_fee = c_uint64(int(1e6)) - 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 - - except: - print "transfer error :0 !!!" - - -if __name__ == "__main__": - checkbalance() \ No newline at end of file diff --git a/kivywallet.kv b/kivywallet.kv index 3451b3d..d7daef4 100644 --- a/kivywallet.kv +++ b/kivywallet.kv @@ -1,7 +1,11 @@ #:kivy 1.8.0 : - cols: 1 + id: rootwidget + daemonchecker_label: daemonchecker_label + daemoninfo_label: daemoninfo_label + namechecker_label: namechecker_label + walletchecker_label: walletchecker_label input_name: name_input input_pw: pw_input label_myaddress: myaddress_label @@ -11,179 +15,257 @@ input_amount: amount_input input_mixin: mixin_input input_paymentid: paymentid_input + minerurl_input: minerurl_input + mineruser_input: mineruser_input + minerpw_input: minerpw_input + minerthreads_input: minerthreads_input - BoxLayout: - size_hint_y: 0.4 - Label: - text: "[b]Kivy Monero/Cryptonote GUI RPC Wallet[/b]" - markup: True - font_size: 20 + AccordionItem: + title: "Start mining" + BoxLayout: + padding: 50,50,50,50 + orientation: 'vertical' + Label: + text: "You must have cpuminer-multi binary in the local directory to use this panel." + Label: + text: "Mining pool address and port\n(e.g. stratum+tcp://pool.moonero.com:1234)" + TextInput: + id: minerurl_input + size_hint_x: 0.7 + Label: + text: "Username/address\n(typically your XMR address)" + TextInput: + id: mineruser_input + font_size: 8 + size_hint_x: 1.7 + Label: + text: "Password\n(typically 'x' or blank)" + TextInput: + id: minerpw_input + size_hint_x: 0.7 + Label: + text: "Number of threads\n(leave blank for max)" + TextInput: + id: minerthreads_input + size_hint_x: 0.4 + Button: + text: "Launch cpuminer" + on_press: root.launchminer() + font_size: 20 - BoxLayout: - size_hint_y: 0.1 + AccordionItem: + title: "Transfer funds" + BoxLayout: + padding: 50,50,50,50 + orientation: 'vertical' + Label: + text: "Send to address:" + size_hint_x: 0.2 + TextInput: + id: address_input + size_hint_x: 2 + font_size: 10 + multiline: False + Label: + text: "Amount MRO:" + size_hint_x: 0.2 + TextInput: + id: amount_input + size_hint_x: 0.8 + font_size: 10 + multiline: False + Label: + text: "Mixin amount:" + size_hint_x: 0.2 + TextInput: + id: mixin_input + size_hint_x: 0.8 + font_size: 10 + multiline: False + Label: + text: "Payment ID:" + size_hint_x: 0.2 + TextInput: + id: paymentid_input + 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: "Initialize wallet file in simplewallet" + BoxLayout: + padding: 50,50,50,50 + orientation: 'vertical' + Label: + id: walletchecker_label + size_hint_y: 0.5 + text: "" + markup: True + font_size: 16 + Button: + text: "Initialize wallet file" + on_press: root.launchwallet() + on_press: root.storeformdata() + font_size: 20 + Label: + id: namechecker_label + text: "" + markup: True + font_size: 16 + BoxLayout: + orientation: 'horizontal' + Label: + text: "Wallet name: " + size_hint_x: 0.3 + font_size: 16 + TextInput: + id: name_input + size_hint_x: 0.7 + font_size: 12 + text: root.walletname + BoxLayout: + orientation: 'horizontal' + Label: + text: "Wallet password: " + size_hint_x: 0.3 + font_size: 16 + TextInput: + id: pw_input + size_hint_x: 0.7 + font_size: 12 + text: root.walletpw + password: True + Label: + text: "Wallet address: \n [size=10](click address to copy)[/size]" + markup: True + font_size: 16 + halign: 'center' + Label: + id: myaddress_label + text: "[ref=addressselection]uninitialized[/ref]" + markup: True + font_size: 10 + on_ref_press: root.selectaddress() + BoxLayout: + orientation: 'horizontal' + Label: + text: "Balance: " + size_hint_x: 0.3 + font_size: 16 + Label: + id: balance_label + text: root.balance + size_hint_x: 0.7 + markup: True + font_size: 16 + BoxLayout: + orientation: 'horizontal' + Label: + text: "Unlocked balance: " + size_hint_x: 0.3 + font_size: 16 + Label: + id: unlockedbalance_label + text: root.unlockedbalance + size_hint_x: 0.7 + markup: True + font_size: 16 + Button: + text: "Save wallet data\n(for faster loading next time)." + on_press: root.savewallet() + font_size: 16 + + AccordionItem: + title: "Launch bitmonerod daemon" + BoxLayout: + padding: 50,50,50,50 + orientation: 'vertical' + Image: + source: 'logo.png' + Label: + markup: True + font_size: 24 + text: "[b]Kivy[/b] Wallet GUI" + Label: + id: daemonchecker_label + text: "" + markup: True + font_size: 18 + Button: + size_hint_y: 1.2 + text: "Start network daemon" + font_size: 20 + on_press: root.launchdaemon() + Label: + size_hint_y: 2.5 + id: daemoninfo_label + markup: True + font_size: 16 + text: "Daemon block goes here..." + halign: 'left' + Button: + text: "Save blockchain info\n(for faster syncing next time)." + on_press: root.save_bc() + font_size: 16 + Button: + text: "Store form data/user input\n(for lesser typing next time)." + on_press: root.storeformdata() + font_size: 16 + + + + size_hint: 0.75, 0.75 + auto_dismiss: False + title: "Save Blockchain Popup" BoxLayout: - size_hint_y: 0.35 - Label: + orientation: 'horizontal' Button: - size_hint_x: 1.7 - text: "Launch bitmonerod daemon" - on_press: root.launchdaemon() - Label: + text: "This may take a minute or three.\nPlease be patient...\nclick to close and save blockchain!" + on_press: root.really_save_bc() + on_press: root.dismiss() Button: - size_hint_x: 1.7 - text: "Launch simplewallet daemon" - on_press: root.launchwallet() - Label: + text: "Click to close without saving and cancel." + on_press: root.dismiss() + + popup_label: popup_label + size_hint: 0.75, 0.75 + auto_dismiss: False + title: "Transfer Popup" BoxLayout: - size_hint_y: 0.2 - - BoxLayout: - padding: 10,0,40,0 - size_hint_y: 0.3 + orientation: 'vertical' Label: - text: "Wallet name: " - size_hint_x: 0.2 - font_size: 16 - TextInput: - id: name_input - size_hint_x: 0.8 - font_size: 12 - text: root.walletname - - BoxLayout: - padding: 10,0,40,0 - size_hint_y: 0.3 - Label: - text: "Wallet password: " - size_hint_x: 0.2 - font_size: 16 - TextInput: - id: pw_input - size_hint_x: 0.8 - font_size: 12 - text: root.walletpw - password: True - - BoxLayout: - size_hint_y: 0.5 - padding: 10,0,80,0 - Label: - text: "Wallet address: \n [size=10](click address to copy)[/size]" + # text: "are you sure?" + id: popup_label + text: "" markup: True - size_hint_x: 0.3 - font_size: 16 - halign: 'center' - Label: - id: myaddress_label - text: "[ref=addressselection]uninitialized[/ref]" - markup: True - size_hint_x: 0.7 - font_size: 10 - on_ref_press: root.selectaddress() + 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_label: txid_label BoxLayout: - size_hint_y: 0.3 + orientation: 'vertical' Label: - text: "Balance: " - size_hint_x: 0.3 - font_size: 16 - - Label: - id: balance_label - text: root.balance - size_hint_x: 0.7 + font_size: 11 markup: True - font_size: 16 - - BoxLayout: - size_hint_y: 0.3 - Label: - text: "Unlocked balance: " - size_hint_x: 0.3 - font_size: 16 - Label: - id: unlockedbalance_label - text: root.unlockedbalance - size_hint_x: 0.7 - markup: True - font_size: 16 - - BoxLayout: - size_hint_y: 0.35 - Label: + id: txid_label + text: "" + on_ref_press: root.selecttxid() Button: - text: "Refresh balance" - on_press: root.checkbalance() - font_size: 20 - Label: - - - BoxLayout: - size_hint_y: 0.2 - - - BoxLayout: - padding: 10,0,40,0 - size_hint_y: 0.3 - Label: - text: "Send to address:" - size_hint_x: 0.2 - TextInput: - id: address_input - size_hint_x: 0.8 - font_size: 10 - multiline: False - - - BoxLayout: - padding: 10,0,40,0 - size_hint_y: 0.3 - Label: - text: "Amount MRO:" - size_hint_x: 0.2 - TextInput: - id: amount_input - size_hint_x: 0.8 - font_size: 10 - multiline: False - - - BoxLayout: - padding: 10,0,40,0 - size_hint_y: 0.3 - Label: - text: "Mixin amount:" - size_hint_x: 0.2 - TextInput: - id: mixin_input - size_hint_x: 0.8 - font_size: 10 - multiline: False - - - BoxLayout: - padding: 10,0,40,0 - size_hint_y: 0.3 - Label: - text: "Payment ID:" - size_hint_x: 0.2 - TextInput: - id: paymentid_input - size_hint_x: 0.8 - font_size: 10 - text: "DISABLED!!! Do not send funds to exchanges. Disabled payment id field is required for depositing on exchanges!" - multiline: False - - - BoxLayout: - padding: 200,0,200,0 - size_hint_y: 0.35 - Button: - text: "Transfer funds" - on_press: root.transferfunds() - font_size: 20 - - BoxLayout: - size_hint_y: 0.1 + text: "OK, dismiss." + on_press: root.dismiss() \ No newline at end of file diff --git a/kivywallet.py b/kivywallet.py index 48b7836..c25bb9a 100644 --- a/kivywallet.py +++ b/kivywallet.py @@ -1,74 +1,289 @@ +# !/usr/bin/python from kivy.app import App -from kivy.uix.gridlayout import GridLayout -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.button import Button -from kivy.uix.label import Label -from kivy.uix.textinput import TextInput +from kivy.uix.accordion import Accordion from kivy.properties import ObjectProperty from kivy.core.clipboard import Clipboard +from kivy.clock import Clock +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.button import Button +from kivy.uix.popup import Popup +from kivy.storage.jsonstore import JsonStore -from functions.bitmonerodloader import bitmonerodloader -from functions.checkbalance import checkbalancerpccall -from functions.transferfunds import transferfundsrpccall -from functions.simplewalletloader import simplewalletloader +from lib.isrunning import isrunning +from lib.checklastblock import checklastblock +from lib.bitmonerodloader import bitmonerodloader +from lib.balancecheck import balancecheck +from lib.namecheck import namecheck +from lib.transferfunds import transferfundsrpccall +from lib.simplewalletloader import simplewalletloader +from lib.save_bc import save_bc +from lib.savewallet import storewallet +from lib.cpuminerloader import cpuminerloader -from sys import argv +from sys import argv, platform +from os.path import isfile +import argparse from multiprocessing import Process, Queue -class RootWidget(GridLayout): + +# Signal that will switch to False on shutdown to kill children +kivysignal = True + + +class SaveBCPopup(Popup): + def really_save_bc(self): + self.storebcthread = Process(target=save_bc) + self.storebcthread.start() + self.storebcthread.join() + self.storebcthread.terminate() + self.storebcthread.join(timeout=3) + + +class TransferPopup(Popup): + 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.popup_label.text = self.popuptext + + def transfer(self): + txid = transferfundsrpccall(self.amount, self.address, self.mixin, self.paymentid) + p = TxIDPopup(txid) + p.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 widget class""" - #Ininitalize variables + # Check to see if wallet name and password passed as arguments if len(argv) == 1: walletname, walletpw = "", "" elif len(argv) == 3: walletname, walletpw = argv[1], argv[2] + # Initialize other variables address, rawaddress = "uninitialized", "uninitialized" balance, unlockedbalance = "uninitialized", "uninitialized" - - #Setup object properties to be modified/used + blockinfotext = "Last block info stats go here..." + namecheckmessage = "uninitialized" + isname = None + # Setup object properties to be modified/used by Kivy + # bitmonerod daemon tab + daemonchecker_label = ObjectProperty() + daemoninfo_label = ObjectProperty() + # init simplewallet tab + namechecker_label = ObjectProperty() + walletchecker_label = ObjectProperty() input_name = ObjectProperty() input_pw = ObjectProperty() label_myaddress = ObjectProperty() label_balance = ObjectProperty() label_unlockedbalance = ObjectProperty() + # transfer tab input_address = ObjectProperty() input_amount = ObjectProperty() input_mixin = ObjectProperty() + input_paymentid = ObjectProperty() + # mining tab + minerurl_input = ObjectProperty() + mineruser_input = ObjectProperty() + minerpw_input = ObjectProperty() + minerthreads_input = ObjectProperty() - walletqueue = Queue() + # Setup queues, signals, and workers + isrunningjobqueue, isrunningresultsqueue = Queue(), Queue() + checklastblockjobqueue, checklastblockresultsqueue = Queue(), Queue() + loadwalletresultsqueue = Queue() + checkbalancejobqueue, checkbalanceresultsqueue = Queue(), Queue() + checknamejobqueue, checknameresultsqueue = Queue(), Queue() + daemonrunning = None + walletrunning = None + minerrunning = None + workers = [ + Process(target=isrunning, + args=(isrunningjobqueue, isrunningresultsqueue)), + Process(target=checklastblock, + args=(checklastblockjobqueue, checklastblockresultsqueue)), + Process(target=balancecheck, + args=(checkbalancejobqueue, checkbalanceresultsqueue)), + Process(target=namecheck, + args=(checknamejobqueue, checknameresultsqueue)) + ] def __init__(self, **kwargs): super(RootWidget, self).__init__(**kwargs) + self.workers[0].start() + self.workers[1].start() + self.workers[2].start() + self.workers[3].start() + Clock.schedule_interval(self.checkisrunning, 0.1) + Clock.schedule_interval(self.getblockinfo, 1) + Clock.schedule_interval(self.checkbalance, 1) + Clock.schedule_interval(self.checkname, 0.1) + # load data if it exists + if isfile('forms.json'): + try: + store = JsonStore('forms.json') + self.input_name.text = store.get('forms')['walletname'] + self.input_address.text = store.get('forms')['sendtoaddress'] + self.input_amount.text = store.get('forms')['amount'] + self.input_mixin.text = store.get('forms')['mixin'] + self.input_paymentid.text = store.get('forms')['paymentid'] + self.minerurl_input.text = store.get('forms')['mineraddress'] + self.mineruser_input.text = store.get('forms')['mineruser'] + self.minerpw_input.text = store.get('forms')['minerpw'] + self.minerthreads_input.text = store.get('forms')[ + 'minerthreads'] + except: + pass + + def checkisrunning(self, dt): + """Check if daemon is running""" + try: + self.daemonrunning, daemoncheckerlabeltext, self.walletrunning, walletcheckerlabeltext, self.minerrunning = self.isrunningresultsqueue.get_nowait() + self.daemonchecker_label.text = daemoncheckerlabeltext + self.walletchecker_label.text = walletcheckerlabeltext + except: + pass + self.isrunningjobqueue.put(kivysignal) + + def getblockinfo(self, dt): + """Try to update daemon stats""" + try: + self.blockinfotext = self.checklastblockresultsqueue.get_nowait() + except: + pass + # blockinfotext = "Last block info:\n No RPC info available." + self.daemoninfo_label.text = self.blockinfotext + self.checklastblockjobqueue.put((self.daemonrunning, kivysignal)) def launchdaemon(self): """launch the bitmonerod daemon in rpc mode""" - bitmonerodloader() + if not self.daemonrunning: + self.launchdaemonthread = Process(target=bitmonerodloader) + self.launchdaemonthread.start() + self.daemonrunning = True + + def save_bc(self): + p = SaveBCPopup() + p.open() + + def checkbalance(self, dt): + """Try to update balance stats""" + try: + self.balance, self.unlockedbalance, self.rawaddress = self.checkbalanceresultsqueue.get_nowait() + self.label_balance.text = self.balance + self.label_unlockedbalance.text = self.unlockedbalance + self.address = '[color=00ffff][ref=addressselection]' + self.rawaddress + '[/ref][/color]' + self.label_myaddress.text = self.address + except: + pass + try: + wallet, self.address, self.rawaddress = self.loadwalletresultsqueue.get_nowait() + self.label_myaddress.text = self.address + except: + pass + self.checkbalancejobqueue.put((self.walletrunning, kivysignal)) def launchwallet(self): """create wallet if necessary and then launch in rpc mode""" - walletthread = Process(target=simplewalletloader, args=(self.input_name.text, self.input_pw.text, self.walletqueue)) - walletthread.start() - walletthread.join() - self.walletname, self.address, self.rawaddress = self.walletqueue.get() - self.input_name.text = self.walletname - self.input_name.background_color = (0.1,0.8,0.8,1) - self.label_myaddress.text = self.address + if not self.walletrunning: + self.walletname = self.input_name.text + self.walletpw = self.input_pw.text + self.launchwalletthread = Process(target=simplewalletloader, + args=(self.walletname, + self.walletpw, + self.isname)) + self.launchwalletthread.start() + self.walletrunning = True + self.storeformdata() - def checkbalance(self): - """get balance info from rpc""" - self.balance, self.unlockedbalance = checkbalancerpccall() - self.label_balance.text = self.balance - self.label_unlockedbalance.text = self.unlockedbalance + def checkname(self, dt): + """Check if wallet name exists in local directory""" + self.walletname = str(self.input_name.text) + self.checknamejobqueue.put((self.walletname, kivysignal)) + if not self.walletname: + self.walletname = "No filename" + # print "no file!" + try: + self.isname, self.namecheckmessage = self.checknameresultsqueue.get_nowait() + # print self.isname + except: + pass + if self.walletrunning: + self.namecheckmessage = "" + self.namechecker_label.text = self.namecheckmessage def selectaddress(self): """copy address to clipboard when user clicks""" print('User clicked on ', self.rawaddress) - Clipboard.put(self.rawaddress) + # Clipboard.put(self.rawaddress) + self.input_address.copy(self.rawaddress) + + def savewallet(self): + self.storewalletthread = Process(target=storewallet) + self.storewalletthread.start() + self.storewalletthread.join(timeout=3) + self.storewalletthread.terminate() + self.storewalletthread.join() def transferfunds(self): """initiate transfer of funds to new address""" - transferfundsrpccall(self.input_amount.text, self.input_address.text, self.input_mixin.text) - self.checkbalance() + + p = TransferPopup(self.input_amount.text, self.input_address.text, + self.input_mixin.text, self.input_paymentid.text) + p.open() + + def transfer(self): + self.transferthread = Process(target=transferfundsrpccall, + args=(self.amount.text, self.address.text, + self.mixin.text, + self.paymentid.text)) + self.transferthread.start() + self.transferthread.join() + self.transferthread.terminate() + self.transferthread.join(timeout=3) + + def launchminer(self): + try: + if not self.minerrunning: + self.launchminerthread = Process(target=cpuminerloader, + args=(self.minerurl_input.text, + self.mineruser_input.text, + self.minerpw_input.text, + self.minerthreads_input.text)) + self.launchminerthread.start() + self.minerrunning = True + self.storeformdata() + except: + pass + + def storeformdata(self): + store = JsonStore('forms.json') + store.put('forms', + walletname=self.input_name.text, + sendtoaddress=self.input_address.text, + amount=self.input_amount.text, + mixin=self.input_mixin.text, + paymentid=self.input_paymentid.text, + mineraddress=self.minerurl_input.text, + mineruser=self.mineruser_input.text, + minerpw=self.minerpw_input.text, + minerthreads=self.minerthreads_input.text) class KivyWalletApp(App): @@ -77,4 +292,10 @@ class KivyWalletApp(App): if __name__ == '__main__': - KivyWalletApp().run() \ No newline at end of file + KivyWalletApp().run() + kivysignal = False + for i in RootWidget.workers: + print i.name + i.terminate() + i.join() + diff --git a/functions/__init__.py b/lib/__init__.py similarity index 100% rename from functions/__init__.py rename to lib/__init__.py diff --git a/lib/balancecheck.py b/lib/balancecheck.py new file mode 100644 index 0000000..eebe865 --- /dev/null +++ b/lib/balancecheck.py @@ -0,0 +1,49 @@ +import requests +import json +import time + +serverURL = 'http://localhost:19091/json_rpc' +headers = {'content-type': 'application/json'} +payload1 = json.dumps({ +"jsonrpc": "2.0", +"method": "getbalance", +"params": {} +}) +payload2 = json.dumps({ +"jsonrpc": "2.0", +"method": "getaddress", +"params": {} +}) + +def balancecheck(jobqueue, resultsqueue): + """function to make rpc call to simplewallet to get current balance""" + kivysignal = True + while kivysignal: + time.sleep(2) + walletrunning, kivysignal = jobqueue.get() + if walletrunning: + #print 'attempting rpc call' + try: + #Make rpc call + resp1 = requests.get(serverURL, headers=headers, data=payload1) + output1 = json.loads(resp1.text) + + #Parse json data to get balance info + rawbalance = str(output1[u'result'][u'balance']/1e12) + rawunlockedbalance = str(output1[u'result'][u'unlocked_balance']/1e12) + + #Format data for kivy + balance = '[color=00ff00]'+rawbalance+'[/color]' + unlockedbalance = '[color=00ff00]'+rawunlockedbalance+'[/color]' + + resp2 = requests.get(serverURL, headers=headers, data=payload2) + output2 = json.loads(resp2.text) + address = str(output2[u'result'][u'address']) + + except: + #Return out of sync if bitmonerod is not ready + balance = '[color=ff0000]out of sync[/color]' + unlockedbalance = '[color=ff0000]out of sync[/color]' + address = '[color=ff0000]out of sync[/color]' + + resultsqueue.put((balance, unlockedbalance, address)) diff --git a/lib/bitmonerodloader.py b/lib/bitmonerodloader.py new file mode 100644 index 0000000..909e372 --- /dev/null +++ b/lib/bitmonerodloader.py @@ -0,0 +1,16 @@ +import subprocess +from sys import platform +import os + +def bitmonerodloader(): + """Start bitmonerod and/or simplewallet and collect wallet name and address""" + print "Start daemon button pressed..." + #Start bitmonerod daemon if not running + if platform == 'win32': + daemonproc = subprocess.Popen("start bitmonerod.exe --rpc-bind-port 18081 --log-level 0", + shell=True) + elif platform == 'linux' or platform == 'linux2' or platform == 'linux32': + # print "linux!" + daemonproc = subprocess.Popen('xterm -bg black -e "./bitmonerod --rpc-bind-port 18081 --log-level 0"', + shell=True) + diff --git a/functions/checkbalance.py b/lib/checkbalance.py similarity index 100% rename from functions/checkbalance.py rename to lib/checkbalance.py diff --git a/lib/checklastblock.py b/lib/checklastblock.py new file mode 100644 index 0000000..8a18b75 --- /dev/null +++ b/lib/checklastblock.py @@ -0,0 +1,55 @@ +import requests +import json +import time +import datetime + +serverURL = 'http://localhost:18081/json_rpc' + +headers = {'content-type': 'application/json'} + +payload = json.dumps({ + "jsonrpc": "2.0", + "id": "test", + "method": "getlastblockheader" +}) + + +def checklastblock(jobqueue, resultsqueue): + """Try and get last block info and return formatted text""" + kivysignal = True + while kivysignal: + time.sleep(1) + daemonrunning, kivysignal = jobqueue.get() + if daemonrunning: + try: + resp = requests.get(serverURL, headers=headers, data=payload) + output = json.loads(resp.text) + # print(output) + reward = output[u'result'][u'block_header'][u'reward'] + linuxblocktime = output[u'result'][u'block_header'][u'timestamp'] + linuxtimenow = time.time() + timesince = linuxtimenow - linuxblocktime + localtime = datetime.datetime.fromtimestamp(int(linuxblocktime + )).strftime('%Y-%m-%d %H:%M:%S') + + blocktext = "Last block height: [color=00ffff]{0}[/color]".format( + output[u'result'][u'block_header'][u'height']) + rewardtext = "Last block reward: [color=00ffff]{0:2.2f}[/color] XMR".format(float(reward)/1e12) + timetext = "Last block time: [color=00ffff]{0}[/color]".format(localtime) + timesincetext = "Time since last block: [color=00ffff]{0:4.2f} s[/color]".format(timesince) + difftext = "Difficulty: [color=00ffff]{0}[/color]".format( + output[u'result'][u'block_header'][u'difficulty']) + orphantext = "Orphan status: [color=00ffff]{0}[/color]".format( + output[u'result'][u'block_header'][u'orphan_status']) + + except: + blocktext = "Last block height: Not connected to daemon..." + rewardtext = "Last block reward: Not connected to daemon..." + timetext = "Last block time: Not connected to daemon..." + timesincetext = "Time since last block: Not connected to daemon..." + difftext = "Difficulty: Not connected to daemon..." + orphantext = "Orphan status: connected to daemon..." + + daemoninfo_labeltext = "[b]Last block info:[/b]\n{0}\n{1}\n{2}\n{3}\n{4}\n{5}".format( + blocktext, rewardtext, timetext, timesincetext, difftext, orphantext) + resultsqueue.put(daemoninfo_labeltext) diff --git a/functions/checkwallet.py b/lib/checkwallet.py similarity index 100% rename from functions/checkwallet.py rename to lib/checkwallet.py diff --git a/lib/cpuminerloader.py b/lib/cpuminerloader.py new file mode 100644 index 0000000..1bc3fe2 --- /dev/null +++ b/lib/cpuminerloader.py @@ -0,0 +1,16 @@ +import subprocess +from sys import platform + + +def cpuminerloader(minerurl, user, pw, threads): + """Start cpuminer""" + print "Start miner button pressed..." + #Start cpuminer daemon if not running + if platform == 'win32': + daemonproc = subprocess.Popen("start minerd.exe -a cryptonight -o {0} -u {1} -p {2} -t {3}".format( + minerurl, user, pw, threads), shell=True) + elif platform == 'linux' or platform == 'linux2' or platform == 'linux32': + print "linux!" + daemonproc = subprocess.Popen('xterm -bg black -e "./minerd -a cryptonight -o {0} -u {1} -p {2} -t {3}"'.format( + minerurl, user, pw, threads), shell=True) + diff --git a/lib/daemoncheck.py b/lib/daemoncheck.py new file mode 100644 index 0000000..e00e746 --- /dev/null +++ b/lib/daemoncheck.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +from sys import platform +import subprocess as sub +import time +from lib.checklastblock import checklastblock + + +def runningcheck(): + """Function to check if daemon is running""" + daemonrunning = False + if platform == 'win32': + import wmi + wmi = wmi.WMI () + for process in wmi.Win32_Process(name='bitmonerod.exe'): + #print process.ProcessId, process.Name + if process.Name: + daemonrunning = True + + elif platform == 'linux' or platform == 'linux2' or platform == 'linux32': + p = sub.Popen("ps -A | grep bitmonerod", shell=True, stdout=sub.PIPE, stderr=sub.PIPE) + output, errors = p.communicate() + if output: + daemonrunning = True + return daemonrunning + +def daemoncheck(queue): + """Check if bitmonerod daemon is running and return values to kivy""" + print "Starting checkdaemon thread on: {0}".format(platform) + + while True: + daemonrunning = runningcheck() + if daemonrunning: + try: + checklastblockoutput = checklastblock() + + except: + checklastblockoutput = 'error' + else: + checklastblockoutput = 'daemon not running' + queue.put((daemonrunning, checklastblockoutput)) + time.sleep(1) + + + +if __name__ == '__main__': + daemoncheck() \ No newline at end of file diff --git a/lib/isrunning.py b/lib/isrunning.py new file mode 100644 index 0000000..733009d --- /dev/null +++ b/lib/isrunning.py @@ -0,0 +1,58 @@ +# !/usr/bin/python +from sys import platform +import subprocess +import time + + +def runningcheck(): + """Function to check if daemon is running""" + daemonrunning, walletrunning, minerrunning = False, False, False + if platform == 'win32': + import wmi + + wmi = wmi.WMI() + for process in wmi.Win32_Process(): + # print process.ProcessId, process.Name + if process.Name == 'bitmonerod.exe': + daemonrunning = True + # print 'daemon' + if process.Name == 'simplewallet.exe': + walletrunning = True + # print 'wallet' + if process.Name == 'minerd.exe': + minerrunning = True + # print 'miner' + elif platform == 'linux' or platform == 'linux2' or platform == 'darwin': + p = subprocess.Popen("ps -A", shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + output, errors = p.communicate() + if 'bitmonerod' in output: + daemonrunning = True + if 'simplewallet' in output: + walletrunning = True + if 'minerd' in output: + minerrunning = True + return daemonrunning, walletrunning, minerrunning + + +def isrunning(jobqueue, resultsqueue): + """Check if bitmonerod daemon is running and return values to kivy""" + print "Starting checkdaemon thread on: {0}".format(platform) + kivysignal = True + while kivysignal: + try: + daemonrunning, walletrunning, minerrunning = runningcheck() + if daemonrunning: + daemoncheckerlabeltext = "[color=00ff00]It appears the bitmonerod daemon is running.[/color]" + else: + daemoncheckerlabeltext = "[color=ff0000]Click the [b]Start network daemon[/b] button to load bitmonerod.[/color]" + if walletrunning: + walletcheckerlabeltext = "[color=00ff00]It appears the simplewallet daemon is running.[/color]" + else: + walletcheckerlabeltext = "[color=00ff00]Click the [b]Initialize wallet file[/b] button to load simplewallet.[/color]" + resultsqueue.put((daemonrunning, daemoncheckerlabeltext, walletrunning, + walletcheckerlabeltext, minerrunning)) + except: + pass + time.sleep(0.1) + kivysignal = jobqueue.get(kivysignal) diff --git a/lib/namecheck.py b/lib/namecheck.py new file mode 100644 index 0000000..ed92d54 --- /dev/null +++ b/lib/namecheck.py @@ -0,0 +1,22 @@ +import os +import time + +def namecheck(jobqueue, resultsqueue): + kivysignal = True + while kivysignal: + try: + walletname, kivysignal = jobqueue.get() + # print walletname + if walletname == "No filename": + message = "Please input a wallet file name..." + isname = None + elif os.path.isfile(walletname): + message = "Wallet file {0} exists in local directory.\nPlease press [b]Initialize wallet file[/b] to [color=00ff00]load wallet file[/color].".format(walletname) + isname = True + if not os.path.isfile(walletname): + message = "Wallet file {0} does not exist in your local directory.\nPlease press [b]Initialize wallet file[/b] to [color=00ff00]generate new wallet file[/color].".format(walletname) + isname = False + resultsqueue.put((isname, message)) + except: + pass + time.sleep(0.1) \ No newline at end of file diff --git a/lib/save_bc.py b/lib/save_bc.py new file mode 100644 index 0000000..1de6adb --- /dev/null +++ b/lib/save_bc.py @@ -0,0 +1,26 @@ +import requests +import json +import time + +serverURL = 'http://localhost:18081/save_bc' + +headers = {'content-type': 'application/json'} + +# payload = json.dumps({ +# "jsonrpc": "2.0", +# "id": "test", +# "method": "save_bc" +# }) + + +def save_bc(): + #resp = requests.get(serverURL, headers=headers, data=payload) + resp = requests.get(serverURL) + # try: + # output = json.loads(resp.text) + # print output + # except: + # print "Waiting for bitmonerod client to sync with network..." + # output = "Waiting for bitmonerod client to sync with network..." + + print resp.text \ No newline at end of file diff --git a/lib/savewallet.py b/lib/savewallet.py new file mode 100644 index 0000000..0a40884 --- /dev/null +++ b/lib/savewallet.py @@ -0,0 +1,25 @@ +import requests +import json +import time + +serverURL = 'http://localhost:19091/json_rpc' + +headers = {'content-type': 'application/json'} + +payload = json.dumps({ + "jsonrpc": "2.0", + "method": "store", + "params": {} +}) + + +def storewallet(): + resp = requests.get(serverURL, headers=headers, data=payload) + try: + output = json.loads(resp.text) + print output + except: + print "Waiting for bitmonerod client to sync with network..." + output = "Waiting for bitmonerod client to sync with network..." + + print output diff --git a/lib/simplewalletloader.py b/lib/simplewalletloader.py new file mode 100644 index 0000000..9e219be --- /dev/null +++ b/lib/simplewalletloader.py @@ -0,0 +1,62 @@ +import os +import time +import subprocess +from sys import platform + + +def simplewalletloader(walletname, walletpw, isname): + """Function to create and/or open wallet in simplewallet rpc mode""" + # Launch simplewallet in rpc mode if wallet file exists + if isname == True: + # os.remove(walletname) + print "***Opening ___" + walletname + "___ wallet file rpc interface***" + if platform == 'win32': + subprocess.Popen( + "start simplewallet.exe --wallet-file {0} --password {1} --rpc-bind-port 19091 --set_log 0\r\nnpause".format( + walletname, walletpw), shell=True) + elif platform == 'linux' or platform == 'linux2' or platform == 'darwin': + subprocess.Popen( + 'xterm -bg black -hold -e "./simplewallet --wallet-file {0} --password {1} --rpc-bind-port 19091"'.format( + walletname, walletpw)) + + # Generate and then launch if wallet file doesn't exist + elif isname == False: + print "Generating wallet file " + walletname + if platform == 'win32': + walletgen = subprocess.Popen( + "simplewallet.exe --generate-new-wallet {0} --password {1} --set_log 0".format( + walletname, walletpw), shell=True, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + elif platform == 'linux' or platform == 'linux2' or platform == 'darwin': + walletgen = subprocess.Popen( + "./simplewallet --generate-new-wallet {0} --password {1} --set_log 0".format( + walletname, walletpw), shell=True, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + print "Wallet gen'ed" + time.sleep(5) + #print walletgen.stdout.read() + #print walletgen.communicate('exit') + walletgen.terminate() + if platform == 'win32': + os.system("taskkill /im simplewallet.exe /f") + elif platform == 'linux' or platform == 'linux2' or platform == 'darwin': + p = subprocess.Popen("pgrep simplewallet", shell=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + output, errors = p.communicate() + output = output.split('\n') + for i in output: + os.system("kill -9 {0}".format(i)) + # Launch simplewallet in rpc mode now that wallet file exists + print "***Opening ___" + walletname + "___ wallet file rpc interface***" + if platform == 'win32': + subprocess.Popen( + "start simplewallet --wallet-file {0} --password {1} --rpc-bind-port 19091 --set_log 0\r\nnpause".format( + walletname, walletpw), shell=True) + elif platform == 'linux' or platform == 'linux2' or platform == 'linux32': + subprocess.Popen( + 'xterm -bg black -hold -e "./simplewallet --wallet-file {0} --password {1} --rpc-bind-port 19091 --set_log 0"'.format( + walletname, walletpw), shell=True) + + + elif isname == None: + pass diff --git a/lib/transferfunds.py b/lib/transferfunds.py new file mode 100644 index 0000000..8404df8 --- /dev/null +++ b/lib/transferfunds.py @@ -0,0 +1,64 @@ +import requests +import json +from ctypes import c_uint64 + +serverURL = 'http://localhost:19091/json_rpc' + + +def transferfundsrpccall(amount, address, mixin, paymentid): + """function to transfer funds to a single address""" + atomicamount = c_uint64(int((float(amount)*1e12)-1e6)) + address = str(address) + mixin = int(mixin) + paymentid = str(paymentid) + mro_fee = c_uint64(int(5e9)) + if len(paymentid) > 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__": + checkbalance() \ No newline at end of file