├── README.md ├── __init__.py ├── icon.ico ├── lib ├── __init__.py ├── __init__.pyc ├── checkbalance.py ├── checkbalance.pyc ├── checklastblock.py ├── checklastblock.pyc ├── getblocks.py ├── gettransfers.py ├── gettransfers.pyc ├── popupTest.py ├── savewallet.py ├── savewallet.pyc ├── transferfunds.py └── transferfunds.pyc ├── lightWallet.kv ├── lightWallet.py ├── lilicon.ico └── logo.png /README.md: -------------------------------------------------------------------------------- 1 | # lightWallet Kivy GUI 2 | 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. By default it uses Atrides open node at http://node.moneroclub.com:8880, but you can change the node it uses in the CONFIG.file stored in the My Documents/lightWallet directory; to use a local bitmonerod instance set it as http://localhost:18081. 3 | 4 | **This program is an alpha version, and although I don't think there is really any way to screw up your wallet, please exercise some modicum of caution.** 5 | 6 | ## Installation 7 | ### Windows, Linux, and Mac 8 | The requirements for running the program from the source files are: Python 2.7, Kivy 1.8, and pygame 1.9.1. The program is also only intended for windows at the moment; it may be semi-functional on linux or mac, but I don't think it will work properly. If you have these requirements, then you just need to add your binary, simplewallet.exe(currently designed to work only with the latest tagged release), 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. 9 | 10 | If you're using the binary (v0.0.3), there are no requirements other than an x64 Windows computer. 11 | 12 | ## Running 13 | It should be OK to launch the program with an instance of bitmonerod and/or simplewallet running. 14 | 15 | 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 your user's My Documents directory in a folder called lightWallet (typically C:\Users\yourUser\Documents\lightWallet) 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. If you would like to create/import a new wallet, you can simply delete CONFIG.file and it will run the initial config prompt again (this won't affect any wallet folders already in the lightWallet directory). 16 | 17 | ## Known issues 18 | If you manage to crash the program, you will probably get an instance of simplewallet.exe hanging around, so to get rid of it you will need to go into the task manager processes tab and end the simplewallet process, or restart your computer. 19 | 20 | The import keys does not work with deprecated wallets (wallets with no electrum seed) at the moment. This is an issue with simplewallet that will be addressed in the next release of simplewallet. 21 | 22 | ## Future Work 23 | There are several features I'd like to implement, the most important of which is probably a tab for an address book, where you can store addresses (and payment ids) that are each a button, and when you click the button it autofills everything on the transfer tab. 24 | 25 | I'd like to figure out why the stdout output of simplewallet.exe lags sometimes, and it doesn't look like the wallet is quite synced up; this doesn't affect anything except making the wallet appear like it is a few blocks behind the daemon or vice-versa. Another thing I want to change is using argparse rather than argv to parse command line arguments. 26 | 27 | I want to clean up the interface and prettify it a bit more too. I'm open to suggestions, so please open an issue or email me if you have any ideas, complaints, etc. -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/__init__.py -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/icon.ico -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/lib/__init__.py -------------------------------------------------------------------------------- /lib/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/lib/__init__.pyc -------------------------------------------------------------------------------- /lib/checkbalance.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | serverURL = 'http://localhost:19091/json_rpc' 5 | headers = {'content-type': 'application/json'} 6 | payload = json.dumps({ 7 | "jsonrpc": "2.0", 8 | "method": "getbalance", 9 | "params": {} 10 | }) 11 | 12 | 13 | def checkBalanceSimplewallet(): 14 | """function to make rpc call to simplewallet to get current balance""" 15 | # print 'Attempting {0} RPC call'.format(CheckBalanceSimplewallet.__name__) 16 | try: 17 | #Make rpc call 18 | resp = requests.get(serverURL, headers=headers, data=payload) 19 | output = json.loads(resp.text) 20 | 21 | #Parse json data to get balance info 22 | balance = str(output[u'result'][u'balance']/1e12) 23 | unlockedbalance = str(output[u'result'][u'unlocked_balance']/1e12) 24 | # print("got balances") 25 | return balance, unlockedbalance 26 | 27 | except: 28 | # Return out of sync if bitmonerod is not ready 29 | # print("couldn't connect") 30 | message = "Can't connect to simplewallet" 31 | return message, message 32 | 33 | 34 | 35 | if __name__ == "__main__": 36 | print checkBalanceSimplewallet() -------------------------------------------------------------------------------- /lib/checkbalance.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/lib/checkbalance.pyc -------------------------------------------------------------------------------- /lib/checklastblock.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | import datetime 5 | 6 | 7 | # Set server for RPC call to bitmonerod 8 | default_URL = 'http://xmr1.coolmining.club:5012/json_rpc' 9 | # Headers, because headers 10 | headers = {'content-type': 'application/json'} 11 | # Data to give to bitmonerod to check last block info 12 | payload = json.dumps({ 13 | "jsonrpc": "2.0", 14 | "id": "test", 15 | "method": "getlastblockheader" 16 | }) 17 | 18 | 19 | def checkLastBlock(serverURL=default_URL): 20 | """Try and get last block info and return formatted text""" 21 | # print('Attempting {0} RPC call'.format(CheckLastBlock.__name__)) 22 | try: 23 | resp = requests.get(serverURL, headers=headers, data=payload) 24 | output = json.loads(resp.text) 25 | # print(output) 26 | height = output[u'result'][u'block_header'][u'height'] 27 | reward = output[u'result'][u'block_header'][u'reward'] 28 | linuxblocktime = output[u'result'][u'block_header'][u'timestamp'] 29 | linuxtimenow = time.time() 30 | timesince = linuxtimenow - linuxblocktime 31 | localtime = datetime.datetime.fromtimestamp(int(linuxblocktime 32 | )).strftime('%Y-%m-%d %H:%M:%S') 33 | return height, reward, timesince, localtime 34 | 35 | 36 | except: 37 | #Return out of sync if bitmonerod is not ready 38 | message = "Can't connect to bitmonerod" 39 | return message, message, message, message 40 | 41 | 42 | if __name__ == "__main__": 43 | checkLastBlock() -------------------------------------------------------------------------------- /lib/checklastblock.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/lib/checklastblock.pyc -------------------------------------------------------------------------------- /lib/getblocks.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | import datetime 5 | import ctypes 6 | 7 | 8 | pyarr = [1] 9 | arr = (ctypes.c_int * len(pyarr))(*pyarr) 10 | # Set server for RPC call to bitmonerod 11 | default_URL = 'http://localhost:18081/gettransactions' 12 | # Headers, because headers 13 | headers = {'content-type': 'application/json'} 14 | # Data to give to bitmonerod to check last block info 15 | payload = json.dumps({'tx ids': ''}) 16 | 17 | 18 | def GetBlocks(serverURL=default_URL): 19 | """Try and get last block info and return formatted text""" 20 | # print('Attempting {0} RPC call'.format(CheckLastBlock.__name__)) 21 | try: 22 | resp = requests.get(serverURL, headers=headers, data=payload) 23 | # output = json.loads(resp.text) 24 | print(resp) 25 | 26 | 27 | except: 28 | #Return out of sync if bitmonerod is not ready 29 | message = "Can't connect to bitmonerod" 30 | return message, message, message, message 31 | 32 | 33 | if __name__ == "__main__": 34 | GetBlocks() -------------------------------------------------------------------------------- /lib/gettransfers.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | 5 | serverURL = 'http://localhost:19091/json_rpc' 6 | 7 | headers = {'content-type': 'application/json'} 8 | 9 | payload = json.dumps({ 10 | "jsonrpc": "2.0", 11 | "method": "incoming_transfers", 12 | "params": {"transfer_type": "all"} 13 | }) 14 | 15 | # payload = json.dumps({ 16 | # "jsonrpc": "2.0", 17 | # "method": "incoming_transfers", 18 | # "params": {"transfer_type": "all"} 19 | # }) 20 | 21 | 22 | def getTransfers(): 23 | unspent_txs, spent_txs = [], [] 24 | try: 25 | resp = requests.get(serverURL, headers=headers, data=payload) 26 | output = json.loads(resp.text) 27 | for i in output[u'result'][u'transfers']: 28 | if i[u'spent'] == False: 29 | unspent_txs.append([i[u'tx_hash'].strip('<>'), int(i[u'amount'])/1e12, i[u'spent']]) 30 | elif i[u'spent'] == True: 31 | spent_txs.append([i[u'tx_hash'].strip('<>'), int(i[u'amount'])/1e12, i[u'spent']]) 32 | except: 33 | pass 34 | return unspent_txs, spent_txs 35 | 36 | 37 | if __name__ == '__main__': 38 | getTransfers() -------------------------------------------------------------------------------- /lib/gettransfers.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/lib/gettransfers.pyc -------------------------------------------------------------------------------- /lib/popupTest.py: -------------------------------------------------------------------------------- 1 | from kivy._event import partial 2 | from kivy.app import App 3 | from kivy.core.clipboard import Clipboard 4 | from kivy.lang import Builder 5 | from kivy.uix.boxlayout import BoxLayout 6 | from kivy.uix.button import Button 7 | 8 | 9 | 10 | Builder.load_string(""" 11 | 12 | BoxLayout: 13 | padding: 30, 30, 30, 30 14 | orientation: "vertical" 15 | Label: 16 | size_hint: 1, 0.2 17 | text: "Double click buttons to copy tx id" 18 | ScrollView: 19 | id: tx_scroller 20 | size_hint: 1, 0.8 21 | GridLayout: 22 | cols: 1 23 | spacing: 7 24 | size_hint_y: None 25 | id: tx_list 26 | """) 27 | 28 | tx_list = [ 29 | ['asdf', 1.234, False], 30 | ['fghj', 2.234, False], 31 | ['xvbb', 3.234, False], 32 | ['wetw', 4.234, False], 33 | ['hjkl', 5.234, False], 34 | ['wret', 6.234, False], 35 | ['bvnm', 7.234, False], 36 | ['zzzz', 8.234, False] 37 | ] 38 | 39 | 40 | class RootWidget(BoxLayout): 41 | """Root Kivy accordion widget class""" 42 | tx_dict = {} 43 | def __init__(self, **kwargs): 44 | super(RootWidget, self).__init__(**kwargs) 45 | for idx, val in enumerate(tx_list): 46 | self.tx_dict[idx] = Button(text="tx_id: {0} | amount: {1:.4f}".format(val[0], val[1]), 47 | size_hint_y=None, height=15, font_size=9) 48 | self.tx_dict[idx].bind(on_press=self._create_button_callback(val[0])) 49 | self.ids.tx_list.add_widget(self.tx_dict[idx]) 50 | 51 | def _create_button_callback(self, val): 52 | def callback(button): 53 | Clipboard.put(val) 54 | return callback 55 | 56 | class lightWalletApp(App): 57 | def build(self): 58 | root = RootWidget() 59 | return root 60 | 61 | 62 | if __name__ == '__main__': 63 | lightWalletApp().run() 64 | 65 | -------------------------------------------------------------------------------- /lib/savewallet.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | 5 | serverURL = 'http://localhost:19091/json_rpc' 6 | 7 | headers = {'content-type': 'application/json'} 8 | 9 | payload = json.dumps({ 10 | "jsonrpc": "2.0", 11 | "method": "store", 12 | "params": {} 13 | }) 14 | 15 | 16 | def storeWallet(): 17 | try: 18 | resp = requests.get(serverURL, headers=headers, data=payload) 19 | output = json.loads(resp.text) 20 | except: 21 | output = "Waiting for bitmonerod client to sync with network..." 22 | # print(output) 23 | return output 24 | 25 | if __name__ == "__main__": 26 | storeWallet() 27 | -------------------------------------------------------------------------------- /lib/savewallet.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/lib/savewallet.pyc -------------------------------------------------------------------------------- /lib/transferfunds.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from ctypes import c_uint64 4 | 5 | serverURL = 'http://localhost:19091/json_rpc' 6 | 7 | 8 | def transferFundsRPCCall(amount, address, mixin, paymentid): 9 | """function to transfer funds to a single address""" 10 | atomicamount = c_uint64(int((float(amount)*1e12))) 11 | address = str(address) 12 | mixin = int(mixin) 13 | paymentid = str(paymentid) 14 | mro_fee = c_uint64(int(1e10)) 15 | if len(paymentid) > 1: 16 | payload = json.dumps({ 17 | "jsonrpc":"2.0", 18 | "method":"transfer", 19 | "params":{ 20 | "destinations":[ 21 | { 22 | "amount":atomicamount.value, 23 | "address":address 24 | } 25 | ], 26 | "fee":mro_fee.value, 27 | "mixin":mixin, 28 | "unlock_time":0, 29 | "payment_id":paymentid 30 | } 31 | }) 32 | else: 33 | payload = json.dumps({ 34 | "jsonrpc":"2.0", 35 | "method":"transfer", 36 | "params":{ 37 | "destinations":[ 38 | { 39 | "amount":atomicamount.value, 40 | "address":address 41 | } 42 | ], 43 | "fee":mro_fee.value, 44 | "mixin":mixin, 45 | "unlock_time":0 46 | } 47 | }) 48 | print payload 49 | 50 | try: 51 | headers = {'content-type': 'application/json'} 52 | resp = requests.get(serverURL, headers=headers, data=payload) 53 | output = json.loads(resp.text) 54 | print output 55 | txid = output[u'result'][u'tx_hash'] 56 | return True, txid 57 | 58 | except: 59 | error = output[u'error'][u'message'] 60 | return False, error 61 | 62 | 63 | if __name__ == "__main__": 64 | transferFundsRPCCall() -------------------------------------------------------------------------------- /lib/transferfunds.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/lib/transferfunds.pyc -------------------------------------------------------------------------------- /lightWallet.kv: -------------------------------------------------------------------------------- 1 | #:kivy 1.8.0 2 | 3 | : 4 | id: root_widget 5 | orientation: "vertical" 6 | root_widget: root_widget 7 | # Tx history tab id items 8 | # empty 9 | # Transfer tab id items 10 | address_input_textinput: address_input_textinput 11 | amount_input_textinput: amount_input_textinput 12 | mixin_input_textinput: mixin_input_textinput 13 | paymentid_input_textinput: paymentid_input_textinput 14 | # Wallet account tab id items 15 | wallet_account_label: wallet_account_label 16 | wallet_address_label: wallet_address_label 17 | wallet_block_label: wallet_block_label 18 | unlocked_balance_label: unlocked_balance_label 19 | total_balance_label: total_balance_label 20 | calculated_balance_label: calculated_balance_label 21 | wallet_savetime_label: wallet_savetime_label 22 | # Bitmonerod daemon tab id items 23 | daemon_server_textinput: daemon_server_textinput 24 | blockheight_label: blockheight_label 25 | last_reward_label: last_reward_label 26 | last_block_time_label: last_block_time_label 27 | time_since_last_label: time_since_last_label 28 | 29 | AccordionItem: 30 | id: tx_accordion 31 | title: "Transaction history" 32 | BoxLayout: 33 | padding: 30, 30, 30, 30 34 | orientation: "vertical" 35 | Label: 36 | text: "Transaction outputs history list (scrollable).\nClick button to copy tx_id." 37 | size_hint_y: 0.1 38 | halign: "center" 39 | MarkupLabel: 40 | text: "[color=00ffff][b]Unspent tx outputs[/color][/b]" 41 | size_hint_y: 0.1 42 | ScrollView: 43 | id: unspent_tx_scroller 44 | size_hint: 1, 0.35 45 | GridLayout: 46 | cols: 1 47 | spacing: 7 48 | size_hint_y: None 49 | id: unspent_tx_list 50 | MarkupLabel: 51 | text: "[color=00ffff][b]Spent tx outputs[/color][/b]" 52 | size_hint_y: 0.1 53 | ScrollView: 54 | id: spent_tx_scroller 55 | size_hint: 1, 0.35 56 | GridLayout: 57 | cols: 1 58 | spacing: 7 59 | size_hint_y: None 60 | id: spent_tx_list 61 | AccordionItem: 62 | title: "Send XMR" 63 | BoxLayout: 64 | padding: 20, 30, 20, 30 65 | orientation: 'vertical' 66 | BoxLayout: 67 | orientation: 'horizontal' 68 | size_hint_y: 0.1 69 | Label: 70 | text: "Send to\naddress:" 71 | size_hint_x: 0.15 72 | TextInput: 73 | id: address_input_textinput 74 | size_hint_x: 0.85 75 | font_size: 9 76 | multiline: False 77 | Label: 78 | size_hint_y: 0.05 79 | BoxLayout: 80 | orientation: 'horizontal' 81 | size_hint_y: 0.1 82 | Label: 83 | text: "Amount XMR:" 84 | size_hint_x: 0.4 85 | TextInput: 86 | id: amount_input_textinput 87 | size_hint_x: 0.6 88 | font_size: 20 89 | multiline: False 90 | Label: 91 | size_hint_y: 0.05 92 | BoxLayout: 93 | orientation: 'horizontal' 94 | size_hint_y: 0.1 95 | Label: 96 | text: "Mixin amount:\n(Please use 0 or >= 2)" 97 | size_hint_x: 0.4 98 | TextInput: 99 | id: mixin_input_textinput 100 | size_hint_x: 0.6 101 | font_size: 20 102 | multiline: False 103 | # Slider: 104 | # min: 0 105 | # max: 10 106 | # value: 1 107 | Label: 108 | size_hint_y: 0.05 109 | BoxLayout: 110 | orientation: 'horizontal' 111 | size_hint_y: 0.1 112 | Label: 113 | text: "Payment ID\n(Needed to send\nto exchanges!):" 114 | size_hint_x: 0.3 115 | TextInput: 116 | id: paymentid_input_textinput 117 | size_hint_x: 0.7 118 | font_size: 9 119 | text: "" 120 | multiline: False 121 | Label: 122 | size_hint_y: 0.05 123 | Button: 124 | size_hint: 1, 0.15 125 | halign: "center" 126 | text: "Transfer funds" 127 | on_press: root.transferfunds() 128 | # on_press: root.storeformdata() 129 | font_size: 20 130 | Label: 131 | size_hint_y: 0.05 132 | # BoxLayout: 133 | # orientation: 'horizontal' 134 | # size_hint_y: 0.15 135 | # MarkupLabel: 136 | # size_hint_x: 0.7 137 | # halign: "center" 138 | # text: "Please donate to Cool Mining for generously\nrunning an open node and supporting lightWallet." 139 | # Button: 140 | # size_hint_x: 0.3 141 | # halign: "center" 142 | # text: "Click to donate XMR\nto Cool Mining Club" 143 | # on_press: root.donate_CMC() 144 | BoxLayout: 145 | orientation: 'horizontal' 146 | size_hint_y: 0.15 147 | MarkupLabel: 148 | size_hint_x: 0.7 149 | halign: "center" 150 | text: "Please donate to Monero core developers\nfor generously donating their time and effort." 151 | Button: 152 | size_hint_x: 0.3 153 | halign: "center" 154 | text: "Click to donate XMR\nto the core Monero devs" 155 | on_press: root.donate_core() 156 | 157 | AccordionItem: 158 | title: "Simplewallet account" 159 | BoxLayout: 160 | orientation: 'vertical' 161 | padding: 30, 30, 30, 30 162 | BoxLayout: 163 | orientation: 'horizontal' 164 | size_hint_y: 0.1 165 | Label: 166 | text: "Account name: " 167 | MarkupLabel: 168 | id: wallet_account_label 169 | BoxLayout: 170 | orientation: 'horizontal' 171 | size_hint_y: 0.15 172 | Label: 173 | size_hint_x: 0.05 174 | text: "Address: " 175 | Label: 176 | size_hint_x: 0.1 177 | MarkupLabel: 178 | id: wallet_address_label 179 | halign: "center" 180 | size_hint_x: 0.85 181 | font_size: 8 182 | on_ref_press: root.selectAddress() 183 | BoxLayout: 184 | orientation: 'horizontal' 185 | size_hint_y: 0.1 186 | Label: 187 | text: "Blocks synced: " 188 | MarkupLabel: 189 | id: wallet_block_label 190 | BoxLayout: 191 | orientation: 'horizontal' 192 | size_hint_y: 0.1 193 | Label: 194 | text: "Unlocked balance: " 195 | MarkupLabel: 196 | id: unlocked_balance_label 197 | BoxLayout: 198 | orientation: 'horizontal' 199 | size_hint_y: 0.1 200 | Label: 201 | text: "Total balance: " 202 | MarkupLabel: 203 | id: total_balance_label 204 | BoxLayout: 205 | orientation: 'horizontal' 206 | size_hint_y: 0.1 207 | Label: 208 | text: "Calculated balance: " 209 | MarkupLabel: 210 | id: calculated_balance_label 211 | Label: 212 | size_hint_y: 0.1 213 | text: "*If you can see a number for the Unlocked and Total balances, then it should be OK to transfer XMR.\nSometimes the Blocks synced lags a bit, but if you see your Unlocked and Total balances, transfers should work." 214 | font_size: 10 215 | BoxLayout: 216 | orientation: 'horizontal' 217 | size_hint_y: 0.25 218 | Button: 219 | text: "Save wallet (Attempts to\nautosave every 3 minutes)" 220 | halign: "center" 221 | on_press: root.saveWallet(0.1) 222 | MarkupLabel: 223 | id: wallet_savetime_label 224 | text: "Wallet last saved @" 225 | 226 | AccordionItem: 227 | title: "Bitmonerod daemon" 228 | BoxLayout: 229 | orientation: 'vertical' 230 | padding: 30, 30, 30, 30 231 | Image: 232 | size_hint_y: 0.3 233 | source: 'logo.png' 234 | Label: 235 | size_hint_y: 0.05 236 | text: "A lightweight Monero GUI account manager" 237 | Label: 238 | size_hint_y: 0.05 239 | Label: 240 | text: "Daemon ip address (default is MoneroClub)\nto use a local instance enter http://localhost:18081\nin CONFIG.file in local directory." 241 | size_hint_y: 0.1 242 | BoxLayout: 243 | size_hint_y: 0.15 244 | orientation: 'horizontal' 245 | padding: 0, 8, 0, 0 246 | MarkupLabel: 247 | size_hint_x: 0.6 248 | color: 0, 1, 0 249 | id: daemon_server_textinput 250 | # text: "http://xmr1.coolmining.club:5012" 251 | # text: "http://localhost:18081" 252 | Button: 253 | text: "Click to change tab\norientation." 254 | halign: "center" 255 | size_hint_x: 0.4 256 | on_press: root.changeTabs() 257 | Label: 258 | size_hint_y: 0.1 259 | BoxLayout: 260 | size_hint_y: 0.05 261 | orientation: 'horizontal' 262 | Label: 263 | text: 'Blockchain height:' 264 | MarkupLabel: 265 | id: blockheight_label 266 | BoxLayout: 267 | size_hint_y: 0.05 268 | orientation: 'horizontal' 269 | Label: 270 | text: 'Last block reward:' 271 | MarkupLabel: 272 | id: last_reward_label 273 | BoxLayout: 274 | size_hint_y: 0.05 275 | orientation: 'horizontal' 276 | Label: 277 | text: 'Last block time:' 278 | MarkupLabel: 279 | id: last_block_time_label 280 | BoxLayout: 281 | size_hint_y: 0.05 282 | orientation: 'horizontal' 283 | Label: 284 | text: 'Time since last block:' 285 | MarkupLabel: 286 | id: time_since_last_label 287 | 288 | 289 | : 290 | id: initial_popup 291 | title: "Initial Setup Window" 292 | size_hint: 0.75, 0.75 293 | auto_dismiss: "False" 294 | BoxLayout: 295 | orientation: 'vertical' 296 | Label: 297 | halign: "center" 298 | text: "It appears that this is your first time loading CryptoNote Kivy GUI\n(or you have deleted your CONFIG.file file).\n\nPlease select if you would like to create a new wallet,\nload an existing wallet, or exit the program." 299 | BoxLayout: 300 | orientation: 'horizontal' 301 | padding: 30, 20, 30, 20 302 | Button: 303 | halign: "center" 304 | text: "Create\nNew\nWallet" 305 | on_press: root.createWalletPopup() 306 | Button: 307 | halign: "center" 308 | text: "Load\nExisting\nWallet" 309 | on_press: root.loadWalletPopup() 310 | Button: 311 | halign: "center" 312 | text: "Exit\nlightWallet" 313 | on_press: root.quit() 314 | 315 | 316 | : 317 | id: create_wallet_popup 318 | title: "Create Wallet Window" 319 | auto_dismiss: False 320 | size_hint: 0.75, 0.75 321 | wallet_name: wallet_name 322 | wallet_pw: wallet_pw 323 | repeat_wallet_pw: repeat_wallet_pw 324 | BoxLayout: 325 | padding: 60, 60, 60, 60 326 | orientation: 'vertical' 327 | MarkupLabel: 328 | text: "[color=#00ffff][b]Wallet name must be alphanumeric (0-9, a-z, A-Z, -, ., (), [], {}, +, _, etc.).\nPassword may also contain special characters such as !, @, #, etc.[/color][/b]" 329 | Label: 330 | text: "Enter wallet name:" 331 | AsciiInput: 332 | id: wallet_name 333 | next: wallet_pw 334 | on_text_validate: root.submitForm() 335 | Label: 336 | text: "Enter wallet password:" 337 | TabTextInput: 338 | id: wallet_pw 339 | password: True 340 | next: repeat_wallet_pw 341 | on_text_validate: root.submitForm() 342 | Label: 343 | text: "Repeat wallet password:" 344 | TabTextInput: 345 | id: repeat_wallet_pw 346 | password: True 347 | next: wallet_name 348 | on_text_validate: root.submitForm() 349 | Button: 350 | text: "Click to create wallet" 351 | on_press: root.submitForm() 352 | 353 | 354 | : 355 | id: load_wallet_popup 356 | title: "Load Wallet Window" 357 | size_hint: 0.75, 0.75 358 | auto_dismiss: False 359 | wallet_path: wallet_path 360 | wallet_pw: wallet_pw 361 | repeat_wallet_pw: repeat_wallet_pw 362 | BoxLayout: 363 | padding: 60, 30, 60, 30 364 | orientation: 'vertical' 365 | MarkupLabel: 366 | size_hint_y: 0.3 367 | halign: "center" 368 | text: "Enter wallet keys path (make sure you include file extension)\nNote: this will copy your keys file to a local subdirectory;\nit will not use your original wallet file(s).\nExample - [b][color=00ffff]C:\\Users\\Satoshi\\SecretStuff\\Wallets\\Monero\\wallet.bin.keys[/b][/color]:" 369 | AsciiSlashInput: 370 | size_hint_y: 0.1 371 | id: wallet_path 372 | next: wallet_pw 373 | on_text_validate: root.submitForm() 374 | Label: 375 | size_hint_y: 0.1 376 | text: "Enter wallet password:" 377 | TabTextInput: 378 | size_hint_y: 0.1 379 | id: wallet_pw 380 | password: True 381 | next: repeat_wallet_pw 382 | on_text_validate: root.submitForm() 383 | Label: 384 | size_hint_y: 0.1 385 | text: "Repeat wallet password:" 386 | TabTextInput: 387 | size_hint_y: 0.1 388 | id: repeat_wallet_pw 389 | password: True 390 | next: wallet_path 391 | on_text_validate: root.submitForm() 392 | Label: 393 | size_hint_y: 0.1 394 | text: "Note - this wallet will be set as your default wallet" 395 | Button: 396 | size_hint_y: 0.15 397 | text: "Click to load wallet" 398 | on_press: root.submitForm() 399 | 400 | 401 | 402 | title: "Password entry popup" 403 | size_hint: 0.75, 0.75 404 | auto_dismiss: False 405 | walletname_label: walletname_label 406 | wallet_pw: wallet_pw 407 | BoxLayout: 408 | orientation: "vertical" 409 | padding: 20, 20, 20, 20 410 | Label: 411 | size_hint: 1, 0.2 412 | id: walletname_label 413 | TabTextInput: 414 | size_hint: 1, 0.1 415 | id: wallet_pw 416 | password: True 417 | on_text_validate: root.submitForm() 418 | Label: 419 | size_hint: 1, 0.2 420 | Button: 421 | text: "Click to launch wallet" 422 | on_press: root.submitForm() 423 | size_hint: 1, 0.5 424 | 425 | 426 | 427 | transfer_popup_label: transfer_popup_label 428 | size_hint: 0.9, 0.9 429 | auto_dismiss: False 430 | title: "Transfer Popup" 431 | BoxLayout: 432 | orientation: 'vertical' 433 | Label: 434 | # text: "are you sure?" 435 | id: transfer_popup_label 436 | text: "" 437 | markup: True 438 | BoxLayout: 439 | orientation: 'horizontal' 440 | Button: 441 | text: "Yes, transfer XMR." 442 | on_press: root.transfer() 443 | on_press: root.dismiss() 444 | Button: 445 | text: "No, cancel transaction." 446 | on_press: root.dismiss() 447 | 448 | 449 | 450 | title: "Tx ID Popup" 451 | address_popup_label: address_popup_label 452 | paymentid_popup_label: paymentid_popup_label 453 | txid_popup_label: txid_popup_label 454 | size_hint: 0.9, 0.9 455 | BoxLayout: 456 | orientation: 'vertical' 457 | padding: 30, 30, 30, 30 458 | MarkupLabel: 459 | halign: "center" 460 | font_size: 9 461 | id: address_popup_label 462 | text: "" 463 | on_ref_press: root.selectaddress() 464 | MarkupLabel: 465 | halign: "center" 466 | font_size: 9 467 | id: paymentid_popup_label 468 | text: "" 469 | on_ref_press: root.selectpaymentid() 470 | MarkupLabel: 471 | halign: "center" 472 | font_size: 9 473 | id: txid_popup_label 474 | text: "" 475 | on_ref_press: root.selecttxid() 476 | Button: 477 | text: "OK, dismiss." 478 | on_press: root.dismiss() 479 | 480 | 481 | 482 | title: "Donate to Cool Mining Club" 483 | size_hint: 0.75, 0.75 484 | donation_amount_CMC: donation_amount_CMC 485 | BoxLayout: 486 | padding: 30, 30, 30, 30 487 | orientation: 'vertical' 488 | Label: 489 | text: "Enter amount of XMR to donate:" 490 | TextInput: 491 | id: donation_amount_CMC 492 | BoxLayout: 493 | Button: 494 | text: "Donate!" 495 | on_press: root.make_donation() 496 | Button: 497 | text: "Cancel :(" 498 | on_press: root.dismiss() 499 | 500 | 501 | 502 | title: "Donate to Monero core devs" 503 | size_hint: 0.75, 0.75 504 | donation_amount_core: donation_amount_core 505 | BoxLayout: 506 | padding: 30, 30, 30, 30 507 | orientation: 'vertical' 508 | Label: 509 | text: "Enter amount of XMR to donate:" 510 | TextInput: 511 | id: donation_amount_core 512 | BoxLayout: 513 | Button: 514 | text: "Donate!" 515 | on_press: root.make_donation() 516 | Button: 517 | text: "Cancel :(" 518 | on_press: root.dismiss() 519 | 520 | 521 | 522 | markup: True 523 | 524 | 525 | 526 | multiline: False -------------------------------------------------------------------------------- /lightWallet.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/python 2 | from kivy.app import App 3 | from kivy.config import Config 4 | from kivy.uix.accordion import Accordion 5 | from kivy.uix.boxlayout import BoxLayout 6 | from kivy.uix.button import Button 7 | from kivy.uix.label import Label 8 | from kivy.properties import ObjectProperty 9 | from kivy.core.clipboard import Clipboard 10 | from kivy.clock import Clock 11 | from kivy.uix.popup import Popup 12 | from kivy.uix.textinput import TextInput 13 | 14 | import os 15 | import sys 16 | import argparse 17 | import time 18 | import datetime 19 | import re 20 | import random 21 | import shutil 22 | 23 | from subprocess import PIPE, Popen, STDOUT 24 | from threading import Thread 25 | from Queue import Queue, Empty 26 | 27 | from lib.checklastblock import checkLastBlock 28 | from lib.savewallet import storeWallet 29 | from lib.transferfunds import transferFundsRPCCall 30 | from lib.checkbalance import checkBalanceSimplewallet 31 | from lib.gettransfers import getTransfers 32 | 33 | 34 | # Set POSIX argument for linux for something... 35 | ON_POSIX = 'posix' in sys.builtin_module_names 36 | 37 | # Donation wallet addy 38 | # donate_CMC_address = "4ATxc8rJjG62ToWqCibuPv7kY9ikds3b3JmRHYqAAymEdhwBGfhtwRgMuHF9bsn18f4wXrK93cw6xdsVCJJwizkiHeUuJKB" 39 | donate_core_address = "46BeWrHpwXmHDpDEUmZBWZfoQpdc6HaERCNmx1pEYL2rAcuwufPN9rXHHtyUA4QVy66qeFQkn6sfK8aHYjA3jk3o1Bv16em" 40 | 41 | # URLs for servers 42 | moneroclub_URL = "http://node.moneroclub.com:8880" 43 | localhost_URL = "http://localhost:18081" 44 | daemon_URL = moneroclub_URL 45 | 46 | # parser gets command line args 47 | parser = argparse.ArgumentParser() 48 | parser.parse_args() 49 | 50 | 51 | # define functions to read output streams 52 | def enqueue_output(out, queue): 53 | for line in iter(out.readline, b''): 54 | queue.put(line) 55 | out.close() 56 | 57 | 58 | # Check if a string is hex only 59 | def hex_match(strg, search=re.compile(r'[^a-fA-F0-9]').search): 60 | return not bool(search(strg)) 61 | 62 | 63 | # Generate random hex strings for payment id 64 | def gen_paymentid(): 65 | return ''.join([random.choice('0123456789ABCDEF') for x in range(64)]) 66 | 67 | 68 | # Function to kill shit on windows 69 | def windows_task_killer(pid): 70 | try: 71 | os.system("taskkill /F /T /PID {0}".format(pid)) 72 | print("Simplewallet process killed with PID {0} killed".format(pid)) 73 | except: 74 | print("This {0} simplewallet instance isn't running".format(pid)) 75 | try: 76 | os.system("tskill {0}".format(pid)) 77 | print("Simplewallet process killed with PID {0} killed".format(pid)) 78 | except: 79 | print("This {0} simplewallet instance isn't running or no tskill".format(pid)) 80 | 81 | 82 | class MarkupLabel(Label): 83 | """ Label class with markup on by default """ 84 | pass 85 | 86 | 87 | class TabTextInput(TextInput): 88 | """ Tab or enter to next TextInput class """ 89 | def __init__(self, *args, **kwargs): 90 | self.next = kwargs.pop('next', None) 91 | super(TabTextInput, self).__init__(*args, **kwargs) 92 | 93 | def set_next(self, next): 94 | self.next = next 95 | 96 | def _keyboard_on_key_down(self, window, keycode, text, modifiers): 97 | key, key_str = keycode 98 | if key == 9 and self.next is not None: 99 | self.next.focus = True 100 | self.next.select_all() 101 | else: 102 | super(TabTextInput, self)._keyboard_on_key_down(window, keycode, text, modifiers) 103 | 104 | 105 | class AsciiInput(TabTextInput): 106 | """ TabTextInput class that only allows ascii input""" 107 | pattern = re.compile('[^0-9a-zA-Z+_=;.\-\(\)\[\]{}]') 108 | def insert_text(self, substring, from_undo=False): 109 | pattern = self.pattern 110 | s = re.sub(pattern, '', substring) 111 | return super(AsciiInput, self).insert_text(s, from_undo=from_undo) 112 | 113 | 114 | class AsciiSlashInput(TabTextInput): 115 | """ TabTextInput class that only allows ascii input and slashes""" 116 | pattern = re.compile('[^0-9a-zA-Z+_=;:/.\-\(\)\[\]{}\\\]') 117 | def insert_text(self, substring, from_undo=False): 118 | pattern = self.pattern 119 | s = re.sub(pattern, '', substring) 120 | return super(AsciiSlashInput, self).insert_text(s, from_undo=from_undo) 121 | 122 | 123 | class InitialPopup(Popup): 124 | """ Initial popup widget """ 125 | def loadWalletPopup(self): 126 | self.dismiss() 127 | lwp = LoadWalletPopup() 128 | lwp.open() 129 | 130 | def createWalletPopup(self): 131 | self.dismiss() 132 | cwp = CreateWalletPopup() 133 | cwp.open() 134 | 135 | def quit(self): 136 | sys.exit(0) 137 | 138 | 139 | class CreateWalletPopup(Popup): 140 | """ Wallet creation screen popup """ 141 | wallet_name = ObjectProperty() 142 | wallet_pw = ObjectProperty() 143 | repeat_wallet_pw = ObjectProperty() 144 | 145 | def __init__(self, *args, **kwargs): 146 | super(CreateWalletPopup, self).__init__(*args, **kwargs) 147 | 148 | def _keyboard_on_key_down(self, window, keycode, text, modifiers): 149 | key, key_str = keycode 150 | print(key, key_str) 151 | if key in (13, 271): 152 | self.submitForm() 153 | else: 154 | super(CreateWalletPopup, self)._keyboard_on_key_down(window, keycode, text, modifiers) 155 | 156 | def submitForm(self): 157 | electrum_coming, electrum_1, electrum_2, electrum_3, electrum_4 = None, None, None, None, None 158 | wallet_name = self.wallet_name.text 159 | wallet_pw = self.wallet_pw.text 160 | wallet_dir = os.path.join(App.get_running_app().root.data_dir, "{0}_walletData".format(wallet_name.replace('.', ''))) 161 | wallet_file = os.path.join(wallet_dir, wallet_name) 162 | if self.wallet_pw.text == self.repeat_wallet_pw.text: 163 | if not os.path.exists(wallet_dir): 164 | os.mkdir(wallet_dir) 165 | else: 166 | sys.exit(7777) 167 | p = Popen(["simplewallet", "--generate-new-wallet", wallet_file, "--password", wallet_pw], 168 | stdout=PIPE, 169 | stdin=PIPE, 170 | bufsize=1, 171 | close_fds=ON_POSIX) 172 | q = Queue() 173 | t = Thread(target=enqueue_output, args=(p.stdout, q)) 174 | t.daemon = True # thread dies with the program 175 | p.stdin.write(str("0\r\n")) 176 | p.stdin.flush() 177 | t.start() 178 | t_start = time.time() 179 | while time.time() - t_start < 2: 180 | try: 181 | line = q.get_nowait() # or q.get(timeout=.1) 182 | if "new wallet:" in line: 183 | address = line.rstrip().split(': ')[2] 184 | if "view key:" in line: 185 | view_key = line.rstrip().split(': ')[1] 186 | if electrum_3: 187 | electrum_line_4 = line.rstrip() 188 | electrum_3 = False 189 | if electrum_2: 190 | electrum_line_3 = line.rstrip() 191 | electrum_3 = True 192 | electrum_2 = False 193 | if electrum_1: 194 | electrum_line_2 = line.rstrip() 195 | electrum_2 = True 196 | electrum_1 = False 197 | if electrum_coming: 198 | electrum_1 = True 199 | electrum_coming = False 200 | if 130 < len(line.rstrip()) < 350: 201 | electrum_coming = True 202 | print(line.rstrip()) 203 | print("simplewallet output:", line.rstrip()) 204 | except Empty: 205 | # print('no output yet') 206 | pass 207 | else: # got line 208 | # print(line) 209 | pass 210 | # time.sleep(0.1) 211 | if sys.platform == 'win32': 212 | try: 213 | p.kill() 214 | print("Simplewallet process killed") 215 | except: 216 | print("Wallet process not running") 217 | try: 218 | windows_task_killer(App.get_running_app().root.wallet_process.pid) 219 | except: 220 | print("Nonetype error") 221 | with open(os.path.join(wallet_dir, "info.txt"), 'w') as f: 222 | f.write("Name:\n{0}\n\nAddress:\n{1}\n\nView key:\n{2}\n\nElectrum seed:\n{3}".format( 223 | wallet_name, address, view_key, electrum_line_2+' '+electrum_line_3+' '+electrum_line_4)) 224 | with open(os.path.join(App.get_running_app().root.data_dir, "CONFIG.file"), 'w') as f: 225 | f.write("Account name= " + wallet_name + "\n") 226 | f.write("Bitmonerod daemon server= {0}".format(daemon_URL)) 227 | self.dismiss() 228 | confirm_box = BoxLayout(orientation='vertical') 229 | confirm_label = Label( 230 | text="Your wallet has been created in the\nMy Documents/lightWallet/{0}_walletData directory.\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.\nClick to dismiss.".format( 231 | wallet_name)) 232 | confirm_button = Button(text="Click to dismiss", size_hint_y=0.2) 233 | confirm_box.add_widget(confirm_label) 234 | confirm_box.add_widget(confirm_button) 235 | walletCreatedPopup = Popup(title="Wallet created window", content=confirm_box, size_hint=(0.75, 0.75), auto_dismiss=False) 236 | confirm_button.bind(on_press=walletCreatedPopup.dismiss) 237 | walletCreatedPopup.open() 238 | App.get_running_app().root.daemon_thread = Thread(target=App.get_running_app().root.checkDaemonStatus, 239 | args=(App.get_running_app().root.daemon_queue, daemon_URL)) 240 | App.get_running_app().root.daemon_thread.daemon = True 241 | App.get_running_app().root.daemon_thread.start() 242 | App.get_running_app().root.launchWallet(wallet_name, wallet_pw) 243 | App.get_running_app().root.daemon_server_textinput.text = daemon_URL 244 | else: 245 | content = Button(text="Check passwords!!!\nClick to dismiss") 246 | pwErrorPopup = Popup(title="Password match error window", 247 | content=content) 248 | content.bind(on_press=pwErrorPopup.dismiss) 249 | pwErrorPopup.open() 250 | 251 | 252 | class LoadWalletPopup(Popup): 253 | """ Wallet loading screen popup """ 254 | wallet_path = ObjectProperty() 255 | wallet_pw = ObjectProperty() 256 | repeat_wallet_pw = ObjectProperty() 257 | 258 | def submitForm(self): 259 | split_name = os.path.split(self.wallet_path.text)[1].split('.') 260 | wallet_name = '.'.join(tuple(split_name[0:len(split_name) - 1])) 261 | print(split_name, wallet_name) 262 | wallet_dir = os.path.join(App.get_running_app().root.data_dir, "{0}_walletData".format(wallet_name.replace('.', ''))) 263 | wallet_file = os.path.join(wallet_dir, wallet_name) 264 | wallet_pw = self.wallet_pw.text 265 | wallet_error = True 266 | if self.wallet_pw.text == self.repeat_wallet_pw.text: 267 | if not os.path.exists(wallet_dir): 268 | os.mkdir(wallet_dir) 269 | else: 270 | sys.exit(7777) 271 | p = Popen(["simplewallet", "--wallet-file", self.wallet_path.text, "--password", wallet_pw], 272 | stdout=PIPE, 273 | stdin=PIPE, 274 | bufsize=1, 275 | close_fds=ON_POSIX) 276 | q = Queue() 277 | t = Thread(target=enqueue_output, args=(p.stdout, q)) 278 | t.daemon = True # thread dies with the program 279 | t.start() 280 | t_start = time.time() 281 | while time.time() - t_start < 2: 282 | try: 283 | line = q.get_nowait() # or q.get(timeout=.1) 284 | print("simplewallet output:", line.rstrip()) 285 | if "Opened wallet: " in line.rstrip(): 286 | address = line.rstrip().split(": ")[1] 287 | if "Error: failed" in line.rstrip(): 288 | wallet_error = True 289 | except Empty: 290 | # print('no output yet') 291 | pass 292 | else: # got line 293 | pass 294 | if sys.platform == 'win32': 295 | try: 296 | p.kill() 297 | print("Simplewallet process killed") 298 | except: 299 | print("Wallet process not running") 300 | try: 301 | windows_task_killer(App.get_running_app().root.wallet_process.pid) 302 | except: 303 | print("Nonetype error") 304 | wallet_error = False 305 | if not wallet_error: 306 | if not os.path.exists(wallet_dir): 307 | os.mkdir(wallet_dir) 308 | with open(os.path.join(App.get_running_app().root.data_dir, "CONFIG.file"), 'w') as f: 309 | f.write("Account name= " + wallet_name + "\n") 310 | f.write("Bitmonerod daemon server= {0}".format(daemon_URL)) 311 | with open(os.path.join(wallet_dir, wallet_name+".address.txt"), 'w') as f: 312 | f.write(address) 313 | shutil.copyfile(self.wallet_path.text, os.path.join(wallet_dir, wallet_name+".keys")) 314 | self.dismiss() 315 | content = Button( 316 | text="Your wallet keys file has been copied to the\nMy Documents/lightWallet/{0}_walletData directory.\nThe original has not been disturbed.\nClick to dismiss.".format( 317 | wallet_name)) 318 | walletCopiedPopup = Popup(title="Wallet keys copied window", content=content, size_hint=(0.75, 0.75)) 319 | content.bind(on_press=walletCopiedPopup.dismiss) 320 | App.get_running_app().root.daemon_server_textinput.text = daemon_URL 321 | walletCopiedPopup.open() 322 | App.get_running_app().root.daemon_thread = Thread(target=App.get_running_app().root.checkDaemonStatus, 323 | args=(App.get_running_app().root.daemon_queue, daemon_URL)) 324 | App.get_running_app().root.daemon_thread.daemon = True 325 | App.get_running_app().root.daemon_thread.start() 326 | App.get_running_app().root.launchWallet(wallet_name, wallet_pw) 327 | else: 328 | content = Button(text="Wallet import error!\nClick to start over") 329 | pwErrorPopup = Popup(title="Wallet import error window", content=content, size_hint=(0.75, 0.75)) 330 | content.bind(on_press=pwErrorPopup.dismiss) 331 | ip = InitialPopup() 332 | content.bind(on_press=ip.open) 333 | pwErrorPopup.open() 334 | 335 | 336 | class PasswordPopup(Popup): 337 | """ Popup class to get pw on launch with CONFIG.file """ 338 | wallet_pw = ObjectProperty() 339 | walletname_label = ObjectProperty() 340 | 341 | def __init__(self, wallet_name): 342 | super(PasswordPopup, self).__init__() 343 | self.wallet_name = wallet_name 344 | if not App.get_running_app().root.wallet_error: 345 | if not App.get_running_app().root.incorrect_password: 346 | self.walletname_label.text = "Please enter your password for {0} account:".format(self.wallet_name) 347 | if App.get_running_app().root.wallet_error: 348 | self.walletname_label.text = "There seems to be a problem with binary file,\nPlease enter your password to attempt rebuild of {0} account:".format( 349 | self.wallet_name) 350 | if App.get_running_app().root.incorrect_password: 351 | self.walletname_label.text = "It seems you've entered an incorrect password,\nPlease re-enter your password to open {0} account:".format( 352 | self.wallet_name) 353 | 354 | def submitForm(self): 355 | wallet_pw = self.wallet_pw.text 356 | App.get_running_app().root.launchWallet(self.wallet_name, wallet_pw) 357 | self.dismiss() 358 | 359 | 360 | class TransferPopup(Popup): 361 | """ Popup class to initiate XMR transfer """ 362 | transfer_popup_label = ObjectProperty() 363 | 364 | def __init__(self, amount, address, mixin, paymentid): 365 | super(TransferPopup, self).__init__() 366 | self.amount = amount 367 | self.address = address 368 | self.mixin = mixin 369 | self.paymentid = paymentid 370 | self.popuptext = "Are you sure you want to send {0:0.4f} XMR\n to [size=9]{1}[/size]\n with mixin count {2} and payment id [size=9]{3}[/size]\n\nAn additional fee of 0.01-0.2 XMR will be required.".format( 371 | float(amount), address, mixin, paymentid) 372 | self.transfer_popup_label.text = self.popuptext 373 | 374 | def transfer(self): 375 | return_boolean, return_value = transferFundsRPCCall(self.amount, self.address, self.mixin, self.paymentid) 376 | txid_popup = TxIDPopup(return_boolean, return_value, self.amount, self.address, self.mixin, self.paymentid) 377 | txid_popup.open() 378 | 379 | 380 | class TxIDPopup(Popup): 381 | address_popup_label = ObjectProperty() 382 | paymentid_popup_label = ObjectProperty() 383 | txid_popup_label = ObjectProperty() 384 | def __init__(self, return_boolean, return_value, amount, address, mixin, paymentid): 385 | super(TxIDPopup, self).__init__() 386 | if return_boolean: 387 | self.address_popup_label.text = "Successfully sent {0:.3f} XMR\nto [ref=address]{1}[/ref]\n(Click address to copy to clipboard)".format(amount, address) 388 | self.paymentid_popup_label.text = "Using mixin count {0}\nand payment id: [ref=paymentid]{1}[/ref]\n(Click payment id to copy to clipboard)".format(mixin, paymentid) 389 | self.txid_popup_label.text = "TxID: [ref=txid]{0}[/ref]\n(Click TxID to copy to clipboard)".format(return_value) 390 | self.txid = return_value 391 | self.address = address 392 | self.paymentid = paymentid 393 | else: 394 | self.txid_popup_label.text = "Transaction error! Error message:\n{0}\nPlease try again by (and maybe lower mixin or amount sent).".format(return_value) 395 | 396 | def selectaddress(self): 397 | Clipboard.put(self.address) 398 | def selectpaymentid(self): 399 | Clipboard.put(self.paymentid) 400 | def selecttxid(self): 401 | Clipboard.put(self.txid) 402 | 403 | 404 | class DonateCMC(Popup): 405 | donation_amount_CMC = ObjectProperty() 406 | 407 | def __init__(self): 408 | super(DonateCMC, self).__init__() 409 | 410 | def make_donation(self): 411 | App.get_running_app().root.transferfunds(donate_CMC_address, 0., self.donation_amount_CMC.text, gen_paymentid()) 412 | self.dismiss() 413 | 414 | 415 | class DonateCore(Popup): 416 | donation_amount_core = ObjectProperty() 417 | 418 | def __init__(self): 419 | super(DonateCore, self).__init__() 420 | 421 | def make_donation(self): 422 | App.get_running_app().root.transferfunds(donate_core_address, 0., self.donation_amount_core.text, gen_paymentid()) 423 | self.dismiss() 424 | 425 | 426 | class RootWidget(Accordion): 427 | """Root Kivy accordion widget class""" 428 | wallet_error = None 429 | incorrect_password = False 430 | data_dir = os.path.join(os.path.expanduser('~'), "Documents", "lightWallet") 431 | # Tx list items 432 | # tx_layout = GridLayout(cols=1, spacing=10, size_hint_y=None) 433 | root_widget = ObjectProperty() 434 | # Transfer XMR id items 435 | address_input_textinput = ObjectProperty() 436 | amount_input_textinput = ObjectProperty() 437 | mixin_input_textinput = ObjectProperty() 438 | paymentid_input_textinput = ObjectProperty() 439 | # Wallet account id items 440 | wallet_account_label = ObjectProperty() 441 | wallet_address_label = ObjectProperty() 442 | unlocked_balance_label = ObjectProperty() 443 | total_balance_label = ObjectProperty() 444 | calculated_balance_label = ObjectProperty() 445 | wallet_savetime_label = ObjectProperty() 446 | # Daemon id items 447 | daemon_server_textinput = ObjectProperty() 448 | blockheight_label = ObjectProperty() 449 | last_reward_label = ObjectProperty() 450 | last_block_time_label = ObjectProperty() 451 | time_since_last_label = ObjectProperty() 452 | wallet_block_label = ObjectProperty() 453 | blockheight = 0 454 | # Process, queue, and thread variables 455 | daemon_queue = Queue() 456 | daemon_thread = None 457 | balance_queue = Queue() 458 | balance_thread = None 459 | wallet_process = None 460 | wallet_queue = Queue() 461 | wallet_thread = None 462 | save_thread = None 463 | save_queue = Queue() 464 | tx_thread = None 465 | tx_queue = Queue() 466 | spent_tx = [] 467 | unspent_tx = [] 468 | spent_tx_dict = {} 469 | unspent_tx_dict = {} 470 | # Other variables 471 | wallet_height = None 472 | calculated_balance = 0.0 473 | total_balance = 0.0 474 | unlocked_balance = 0.0 475 | temp_tx_amount = 0.0 476 | temp_tx_hash = "" 477 | temp_tx_paymentid = "" 478 | wallet_save_time = None 479 | donate_CMC_input_amount = ObjectProperty() 480 | 481 | def __init__(self, **kwargs): 482 | super(RootWidget, self).__init__(**kwargs) 483 | if not os.path.isdir(self.data_dir): 484 | os.makedirs(self.data_dir) 485 | self.daemon_server_textinput.text = daemon_URL 486 | if os.path.isfile(os.path.join(self.data_dir, 'CONFIG.file')): 487 | with open(os.path.join(self.data_dir, 'CONFIG.file'), 'r') as f: 488 | lines = f.readlines() 489 | try: 490 | self.wallet_name = lines[0].rstrip().split('=')[1].lstrip() 491 | self.daemon_server_textinput.text = lines[1].rstrip().split('=')[1].lstrip() 492 | self.daemon_thread = Thread(target=self.checkDaemonStatus, 493 | args=(self.daemon_queue, self.daemon_server_textinput.text)) 494 | self.daemon_thread.daemon = True 495 | self.daemon_thread.start() 496 | Clock.schedule_once(self.initialPassword, 0.5) 497 | except: 498 | Clock.schedule_once(self.initialConfig, 0.5) 499 | else: 500 | Clock.schedule_once(self.initialConfig, 0.5) 501 | self.balance_thread = Thread(target=self.checkBalanceStatus, args=(self.balance_queue,)) 502 | self.balance_thread.daemon = True 503 | self.balance_thread.start() 504 | self.tx_thread = Thread(target=self.checkTransactionsStatus, args=(self.tx_queue,)) 505 | self.tx_thread.daemon = True 506 | self.tx_thread.start() 507 | Clock.schedule_interval(self.checkDaemon, 7) 508 | Clock.schedule_interval(self.checkBalance, 7) 509 | Clock.schedule_interval(self.checkTransactions, 12) 510 | Clock.schedule_interval(self.saveWallet, 180) 511 | self.ids.unspent_tx_list.bind(minimum_height=self.ids.unspent_tx_list.setter('height')) 512 | self.ids.spent_tx_list.bind(minimum_height=self.ids.spent_tx_list.setter('height')) 513 | 514 | def initialConfig(self, dt): 515 | ip = InitialPopup() 516 | ip.open() 517 | 518 | def initialPassword(self, dt): 519 | gp = PasswordPopup(self.wallet_name) 520 | gp.open() 521 | 522 | def checkDaemonStatus(self, daemon_queue, server): 523 | """Method to be threaded for checking bitmonerod status""" 524 | while True: 525 | height, reward, timesince, localtime = checkLastBlock(server + "/json_rpc") 526 | daemon_queue.put((height, reward, timesince, localtime)) 527 | time.sleep(5) 528 | 529 | def checkDaemon(self, dt): 530 | """RPC call to check bitmonerod for blockchain info""" 531 | try: 532 | height, reward, timesince, localtime = self.daemon_queue.get_nowait() 533 | self.blockheight = int(height) 534 | self.blockheight_label.text = "[b][color=00ffff]{0}[/b][/color]".format(height) 535 | self.last_reward_label.text = "[b][color=00ffff]{0:.2f}[/b][/color] XMR".format(reward * 1e-12) 536 | self.last_block_time_label.text = "[b][color=00ffff]{0}[/b][/color]".format(localtime) 537 | self.time_since_last_label.text = "[b][color=00ffff]{0:.0f}[/b][/color] seconds".format(timesince) 538 | self.wallet_block_label.text = "[b][color=00ffff]{0}[/b][/color] of [b][color=00ffff]{1}[/b][/color]".format( 539 | self.wallet_height, self.blockheight) 540 | except: 541 | self.blockheight_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No daemon connection") 542 | self.last_reward_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No daemon connection") 543 | self.last_block_time_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No daemon connection") 544 | self.time_since_last_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No daemon connection") 545 | self.wallet_block_label.text = "[b][color=ff0000]{0}[/b][/color] of [b][color=ff0000]{1}[/b][/color]".format( 546 | self.wallet_height, "No daemon connection") 547 | 548 | def checkBalanceStatus(self, balance_queue): 549 | """Method to be threaded for checking balance status""" 550 | while True: 551 | balance, unlocked_balance = checkBalanceSimplewallet() 552 | balance_queue.put((balance, unlocked_balance)) 553 | time.sleep(5) 554 | 555 | def checkBalance(self, dt): 556 | """RPC call to check bitmonerod for blockchain info""" 557 | try: 558 | balance, unlocked_balance = self.balance_queue.get_nowait() 559 | try: 560 | self.total_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(float(balance)) 561 | self.unlocked_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(float(unlocked_balance)) 562 | self.unlocked_balance, self.total_balance = float(unlocked_balance), float(balance) 563 | except ValueError: 564 | self.total_balance_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No wallet connection or syncing") 565 | self.unlocked_balance_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No wallet connection or syncing") 566 | except Empty: 567 | # print("empty") 568 | self.total_balance_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No wallet connection or syncing") 569 | self.unlocked_balance_label.text = "[b][color=ff0000]{0}[/b][/color]".format("No wallet connection or syncing") 570 | 571 | def checkTransactionsStatus(self, tx_queue): 572 | """Method to be threaded for checking balance status""" 573 | while True: 574 | unspent_txs, spent_txs = getTransfers() 575 | tx_queue.put((unspent_txs, spent_txs)) 576 | time.sleep(10) 577 | 578 | def checkTransactions(self, dt): 579 | """RPC call to check bitmonerod for blockchain info""" 580 | try: 581 | unspent_txs, spent_txs = self.tx_queue.get_nowait() 582 | if len(unspent_txs) > len(self.unspent_tx) or len(spent_txs) > len(self.spent_tx): 583 | self.unspent_tx = unspent_txs 584 | self.spent_tx = spent_txs 585 | self.unspent_tx_dict = {} 586 | self.spent_tx_dict = {} 587 | self.ids.unspent_tx_list.clear_widgets() 588 | self.ids.spent_tx_list.clear_widgets() 589 | temp_balance = 0.0 590 | for idx, val in enumerate(self.unspent_tx): 591 | self.unspent_tx_dict[idx] = Button(text="tx_id: {0} | amount: {1:.4f}".format(val[0], val[1]), 592 | size_hint_y=None, height=15, font_size=9) 593 | self.unspent_tx_dict[idx].bind(on_press=self._create_button_callback(val[0])) 594 | self.ids.unspent_tx_list.add_widget(self.unspent_tx_dict[idx]) 595 | temp_balance += val[1] 596 | for idx, val in enumerate(self.spent_tx): 597 | self.spent_tx_dict[idx] = Button(text="tx_id: {0} | amount: {1:.4f}".format(val[0], val[1]), 598 | size_hint_y=None, height=15, font_size=9) 599 | self.spent_tx_dict[idx].bind(on_press=self._create_button_callback(val[0])) 600 | self.ids.spent_tx_list.add_widget(self.spent_tx_dict[idx]) 601 | self.calculated_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(float(temp_balance)) 602 | except Empty: 603 | pass 604 | 605 | def _create_button_callback(self, val): 606 | def callback(button): 607 | Clipboard.put(val) 608 | return callback 609 | 610 | def launchWallet(self, wallet_name, wallet_pw): 611 | self.wallet_name = wallet_name 612 | self.wallet_pw = wallet_pw 613 | wallet_dir = os.path.join(self.data_dir, "{0}_walletData".format(wallet_name.replace('.', ''))) 614 | wallet_file = os.path.join(wallet_dir, wallet_name) 615 | address_file = os.path.join(wallet_dir, wallet_name+".address.txt") 616 | if not os.path.isfile(wallet_file): 617 | print("binary file not found, creating...") 618 | p = Popen(["simplewallet.exe", "--wallet-file", wallet_file, "--password", wallet_pw], 619 | stdin=PIPE, 620 | stdout=PIPE, 621 | stderr=STDOUT, 622 | bufsize=1) 623 | q = Queue() 624 | t = Thread(target=enqueue_output, args=(p, q)) 625 | t.daemon = True # thread dies with the program 626 | t.start() 627 | t_start = time.time() 628 | while time.time() - t_start < 2: 629 | try: 630 | line = q.get_nowait() # or q.get(timeout=.1) 631 | print("simplewallet output:", line.rstrip()) 632 | if "Opened wallet: " in line.rstrip(): 633 | address = line.rstrip().split(": ")[1] 634 | if "Error: failed" in line.rstrip(): 635 | wallet_error = True 636 | except Empty: 637 | # print('no output yet') 638 | pass 639 | else: # got line 640 | pass 641 | try: 642 | p.communicate(input="exit\r\n") 643 | except: 644 | pass 645 | time.sleep(2) 646 | if sys.platform == 'win32': 647 | try: 648 | p.kill() 649 | print("Simplewallet process killed") 650 | except: 651 | print("Wallet process not running") 652 | try: 653 | windows_task_killer(App.get_running_app().root.wallet_process.pid) 654 | except: 655 | print("Nonetype error") 656 | if not os.path.isfile(address_file): 657 | with open(address_file, 'w') as f: 658 | f.write(address) 659 | self.address = address 660 | if os.path.isfile(address_file): 661 | with open(address_file, 'r') as f: 662 | self.address = f.read().rstrip() 663 | time.sleep(2) 664 | self.calculated_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(self.calculated_balance) 665 | self.wallet_process = Popen(["simplewallet", "--wallet-file", wallet_file, "--password", wallet_pw, 666 | "--daemon-address", self.daemon_server_textinput.text, "--rpc-bind-port", "19091"], 667 | stdout=PIPE, 668 | stderr=STDOUT, 669 | bufsize=1) 670 | self.wallet_thread = Thread(target=enqueue_output, 671 | args=(self.wallet_process.stdout, self.wallet_queue)) 672 | self.wallet_thread.daemon = True # thread dies with the program 673 | self.wallet_thread.start() 674 | self.wallet_account_label.text = "[b][color=00ffff]{0}[/b][/color]".format(wallet_name) 675 | self.wallet_address_label.text = "[b][color=00ffff][ref=Address]{0}\n[size=14]Click to copy[/b][/color][/size][/ref]".format(self.address) 676 | Clock.schedule_interval(self.readWalletQueue, 1e-3) 677 | 678 | def readWalletQueue(self, dt): 679 | wallet_dir = os.path.join(self.data_dir, "{0}_walletData".format(self.wallet_name.replace('.', ''))) 680 | wallet_file = os.path.join(wallet_dir, self.wallet_name) 681 | try: 682 | new_line = self.wallet_queue.get_nowait() 683 | # print(new_line) 684 | if "Wallet initialize failed: failed to read file" in new_line: 685 | self.wallet_thread.join() 686 | self.wallet_error = True 687 | os.remove(wallet_file) 688 | Clock.schedule_once(self.initialPassword, 0.5) 689 | if "Wallet initialize failed: invalid password" in new_line: 690 | self.wallet_thread.join() 691 | self.incorrect_password = True 692 | Clock.schedule_once(self.initialPassword, 0.5) 693 | if "height:" in new_line: 694 | try: 695 | self.wallet_height = new_line.split(", ")[1].split(": ")[1] 696 | self.wallet_block_label.text = "[b][color=00ffff]{0}[/b][/color] of [b][color=00ffff]{1}[/b][/color]".format( 697 | self.wallet_height, self.blockheight) 698 | except: 699 | print("height: read failed") 700 | elif "height " in new_line: 701 | try: 702 | self.wallet_height = new_line.split(", ")[1].split(" ")[1] 703 | self.wallet_block_label.text = "[b][color=00ffff]{0}[/b][/color] of [b][color=00ffff]{1}[/b][/color]".format( 704 | self.wallet_height, self.blockheight) 705 | except: 706 | print("height read failed") 707 | elif "Refresh done," in new_line: 708 | # print("refresh done") 709 | split_line = new_line.split(',') 710 | self.total_balance = float(split_line[2].split(': ')[1]) 711 | self.unlocked_balance = float(split_line[3].split(': ')[1]) 712 | self.total_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(self.total_balance) 713 | self.unlocked_balance_label.text = "[b][color=00ffff]{0:.4f}[/b][/color] XMR".format(self.unlocked_balance) 714 | except Empty: 715 | pass 716 | 717 | def saveWallet(self, dt): 718 | try: 719 | save_thread = Thread(target=self.storeWallet) 720 | save_thread.daemon = True 721 | save_thread.start() 722 | save_thread.join() 723 | except: 724 | print("Save failed") 725 | 726 | def storeWallet(self): 727 | response = storeWallet() 728 | try: 729 | response = storeWallet() 730 | if "jsonrpc" in response: 731 | local_save_time = datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S') 732 | self.wallet_savetime_label.text = "Wallet saved @\n[b][color=00ffff]{0}[/b][/color]".format(local_save_time) 733 | print("Finished save thread") 734 | elif "Waiting for" in response: 735 | self.wallet_savetime_label.text = "[color=ff0000]Wallet save failed\nprobably syncing[/color]" 736 | except: 737 | print("Major save fail!") 738 | 739 | def selectAddress(self): 740 | """Copy address to clipboard when user clicks""" 741 | print('User clicked on ', self.address) 742 | Clipboard.put(self.address) 743 | 744 | def transferfunds(self, address=None, mixin=None, amount=None, paymentid=None): 745 | if not address: 746 | address = self.address_input_textinput.text 747 | if not mixin: 748 | mixin = self.mixin_input_textinput.text 749 | if not amount: 750 | amount = self.amount_input_textinput.text 751 | if not paymentid: 752 | paymentid = self.paymentid_input_textinput.text 753 | print(address, mixin, amount, paymentid) 754 | print(len(paymentid), hex_match(paymentid)) 755 | """Initiate transfer of funds to new address""" 756 | address_error_content = Button(text="Address must be 95 characters long\nand start with a '4'.\nClick to start over.", 757 | halign="center") 758 | address_error_popup = Popup(title="Address error", content=address_error_content, size_hint=(0.75, 0.75)) 759 | address_error_content.bind(on_press=address_error_popup.dismiss) 760 | mixin_error_content = Button(text="Mixin must be an integer between 0 and 99.\nClick to start over.", 761 | halign="center") 762 | mixin_error_popup = Popup(title="Mixin error", content=mixin_error_content, size_hint=(0.75, 0.75)) 763 | mixin_error_content.bind(on_press=mixin_error_popup.dismiss) 764 | if self.unlocked_balance > 0.01: 765 | amount_error_content = Button(text="Amount must be a number between 0.01 and {0:.3f} XMR.\nClick to start over.".format(self.unlocked_balance), 766 | halign="center") 767 | else: 768 | amount_error_content = Button(text="Amount must be a number greater than 0.01 XMR.\nClick to start over.".format(self.unlocked_balance), 769 | halign="center") 770 | amount_error_popup = Popup(title="Amount error", content=amount_error_content, size_hint=(0.75, 0.75)) 771 | amount_error_content.bind(on_press=amount_error_popup.dismiss) 772 | paymentid_error_content = Button(text="Payment ID must be 0 or 64 hex characters long.\nClick to start over.", 773 | halign="center") 774 | paymentid_error_popup = Popup(title="Payment ID error", content=paymentid_error_content, size_hint=(0.75, 0.75)) 775 | paymentid_error_content.bind(on_press=paymentid_error_popup.dismiss) 776 | try: 777 | address = address 778 | if len(address) != 95 or address[0] != '4': 779 | address_error_popup.open() 780 | return 781 | except IndexError: 782 | address_error_popup.open() 783 | return 784 | try: 785 | if not mixin: 786 | mixin = 0 787 | mixin = int(mixin) 788 | if mixin == 1: 789 | mixin = 0 790 | print(mixin) 791 | if not 0 <= mixin < 100: 792 | mixin_error_popup.open() 793 | return 794 | except ValueError: 795 | mixin_error_popup.open() 796 | return 797 | try: 798 | amount = float(amount) 799 | if not 0.01 <= amount <= self.unlocked_balance: 800 | amount_error_popup.open() 801 | return 802 | except ValueError: 803 | amount_error_popup.open() 804 | return 805 | try: 806 | paymentid = paymentid 807 | if len(paymentid) not in (0, 64) or not hex_match(paymentid): 808 | paymentid_error_popup.open() 809 | return 810 | except ValueError: 811 | paymentid_error_popup.open() 812 | return 813 | tp = TransferPopup(amount, address, mixin, paymentid) 814 | tp.open() 815 | 816 | def donate_CMC(self): 817 | CMC_donate_popup = DonateCMC() 818 | CMC_donate_popup.open() 819 | 820 | def donate_core(self): 821 | core_donate_popup = DonateCore() 822 | core_donate_popup.open() 823 | 824 | def changeTabs(self): 825 | print(self.root_widget.orientation) 826 | if self.root_widget.orientation == "horizontal": 827 | self.root_widget.orientation = "vertical" 828 | else: 829 | self.root_widget.orientation = "horizontal" 830 | 831 | 832 | class lightWalletApp(App): 833 | def build(self): 834 | root = RootWidget() 835 | return root 836 | 837 | 838 | if __name__ == '__main__': 839 | Config.set('kivy', 'exit_on_escape', 0) 840 | Config.set('kivy', 'window_icon', "./lilicon.ico") 841 | Config.set('input', 'mouse', 'mouse,disable_multitouch') 842 | lightWalletApp().run() 843 | try: 844 | tx_file = os.path.join(App.get_running_app().root.data_dir, 845 | "{0}_walletData".format(App.get_running_app().root.wallet_name.replace('.', '')), "tx.txt") 846 | with open(tx_file, 'w') as txs: 847 | txs.write("Unspent tx outputs:\n") 848 | for i in App.get_running_app().root.unspent_tx: 849 | txs.write("tx_id: {0} | amount: {1:.4f}\n".format(i[0], i[1])) 850 | txs.write("\nSpent tx outputs:\n") 851 | for i in App.get_running_app().root.spent_tx: 852 | txs.write("tx_id: {0} | amount: {1:.4f}\n".format(i[0], i[1])) 853 | except: 854 | print("Problem writing tx file") 855 | if sys.platform == 'win32': 856 | try: 857 | App.get_running_app().root.wallet_process.kill() 858 | print("Simplewallet process killed") 859 | except: 860 | print("Wallet process not running") 861 | try: 862 | windows_task_killer(App.get_running_app().root.wallet_process.pid) 863 | except: 864 | print("Nonetype error") 865 | # time.sleep(1.5) 866 | if os.path.isfile("./simplewallet.log"): 867 | try: 868 | os.remove("./simplewallet.log") 869 | except WindowsError: 870 | print("File in use, can not be deleted!") 871 | 872 | -------------------------------------------------------------------------------- /lilicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/lilicon.ico -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jwinterm/lightWallet/ce1a0292b6977ec81838765c500aaf343c752f9a/logo.png --------------------------------------------------------------------------------