commit 8068fc74a9e3294b19c95438bb7e3c24c35c9298 Author: jwinterm Date: Sun May 25 16:02:48 2014 -0500 Initial commit for Kivy GUI wallet diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/functions/__init__.py b/functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/functions/bitmonerodloader.py b/functions/bitmonerodloader.py new file mode 100644 index 0000000..53fe8cd --- /dev/null +++ b/functions/bitmonerodloader.py @@ -0,0 +1,15 @@ +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/checkbalance.py b/functions/checkbalance.py new file mode 100644 index 0000000..9f17396 --- /dev/null +++ b/functions/checkbalance.py @@ -0,0 +1,38 @@ +import requests +import json + +serverURL = 'http://localhost:8082/json_rpc' + + +def checkbalancerpccall(): + """function to make rpc call to simplewallet to get current balance""" + payload = json.dumps({ + "jsonrpc": "2.0", + "method": "getbalance", + "params": {} + }) + print 'attempting rpc call' + try: + #Make rpc call + headers = {'content-type': 'application/json'} + resp = requests.get(serverURL, headers=headers, data=payload) + output = json.loads(resp.text) + + #Parse json data to get balance info + rawbalance = str(output[u'result'][u'balance']/1e12) + rawunlockedbalance = str(output[u'result'][u'unlocked_balance']/1e12) + + #Format data for kivy + balance = '[color=00ff00]'+rawbalance+'[/color]' + unlockedbalance = '[color=00ff00]'+rawunlockedbalance+'[/color]' + return balance, unlockedbalance + + + except: + #Return out of sync if bitmonerod is not ready + return '[color=ff0000]out of sync[/color]', '[color=ff0000]out of sync[/color]' + + + +if __name__ == "__main__": + checkbalance() \ No newline at end of file diff --git a/functions/checklastblock.py b/functions/checklastblock.py new file mode 100644 index 0000000..e7b61db --- /dev/null +++ b/functions/checklastblock.py @@ -0,0 +1,42 @@ +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/checkwallet.py b/functions/checkwallet.py new file mode 100644 index 0000000..5fef54b --- /dev/null +++ b/functions/checkwallet.py @@ -0,0 +1,22 @@ +import requests +import json + +serverURL = 'http://localhost:8082/json_rpc' + +payload = json.dumps({ + "jsonrpc": "2.0", + "method": "store", + "params": {} +}) + + +def checkwalletrpccall(): + headers = {'content-type': 'application/json'} + resp = requests.get(serverURL, headers=headers, data=payload) + output = json.loads(resp.text) + print output + +# return output[u'result'][u'balance'], output[u'result'][u'unlocked_balance'] + +if __name__ == "__main__": + checkwallet() \ No newline at end of file diff --git a/functions/simplewalletloader.py b/functions/simplewalletloader.py new file mode 100644 index 0000000..4236dff --- /dev/null +++ b/functions/simplewalletloader.py @@ -0,0 +1,43 @@ +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 new file mode 100644 index 0000000..05bde1a --- /dev/null +++ b/functions/transferfunds.py @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000..3451b3d --- /dev/null +++ b/kivywallet.kv @@ -0,0 +1,189 @@ +#:kivy 1.8.0 + +: + cols: 1 + input_name: name_input + input_pw: pw_input + label_myaddress: myaddress_label + label_balance: balance_label + label_unlockedbalance: unlockedbalance_label + input_address: address_input + input_amount: amount_input + input_mixin: mixin_input + input_paymentid: paymentid_input + + BoxLayout: + size_hint_y: 0.4 + Label: + text: "[b]Kivy Monero/Cryptonote GUI RPC Wallet[/b]" + markup: True + font_size: 20 + + BoxLayout: + size_hint_y: 0.1 + + BoxLayout: + size_hint_y: 0.35 + Label: + Button: + size_hint_x: 1.7 + text: "Launch bitmonerod daemon" + on_press: root.launchdaemon() + Label: + Button: + size_hint_x: 1.7 + text: "Launch simplewallet daemon" + on_press: root.launchwallet() + Label: + + BoxLayout: + size_hint_y: 0.2 + + BoxLayout: + padding: 10,0,40,0 + size_hint_y: 0.3 + 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]" + 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: + size_hint_y: 0.3 + 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: + 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: + 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 diff --git a/kivywallet.py b/kivywallet.py new file mode 100644 index 0000000..48b7836 --- /dev/null +++ b/kivywallet.py @@ -0,0 +1,80 @@ +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.properties import ObjectProperty +from kivy.core.clipboard import Clipboard + +from functions.bitmonerodloader import bitmonerodloader +from functions.checkbalance import checkbalancerpccall +from functions.transferfunds import transferfundsrpccall +from functions.simplewalletloader import simplewalletloader + +from sys import argv +from multiprocessing import Process, Queue + +class RootWidget(GridLayout): + """Root Kivy widget class""" + #Ininitalize variables + if len(argv) == 1: + walletname, walletpw = "", "" + elif len(argv) == 3: + walletname, walletpw = argv[1], argv[2] + address, rawaddress = "uninitialized", "uninitialized" + balance, unlockedbalance = "uninitialized", "uninitialized" + + #Setup object properties to be modified/used + input_name = ObjectProperty() + input_pw = ObjectProperty() + label_myaddress = ObjectProperty() + label_balance = ObjectProperty() + label_unlockedbalance = ObjectProperty() + input_address = ObjectProperty() + input_amount = ObjectProperty() + input_mixin = ObjectProperty() + + walletqueue = Queue() + + def __init__(self, **kwargs): + super(RootWidget, self).__init__(**kwargs) + + def launchdaemon(self): + """launch the bitmonerod daemon in rpc mode""" + bitmonerodloader() + + 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 + + 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 selectaddress(self): + """copy address to clipboard when user clicks""" + print('User clicked on ', self.rawaddress) + Clipboard.put(self.rawaddress) + + 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() + + +class KivyWalletApp(App): + def build(self): + return RootWidget() + + +if __name__ == '__main__': + KivyWalletApp().run() \ No newline at end of file