Files
lightWallet/lightWallet.py
T
2014-11-22 12:38:24 -06:00

561 lines
24 KiB
Python

# !/usr/bin/python
from platform import system
from kivy.app import App
from kivy.config import Config
from kivy.uix.accordion import Accordion
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.core.clipboard import Clipboard
from kivy.clock import Clock
from kivy.uix.popup import Popup
from kivy.storage.jsonstore import JsonStore
from kivy.uix.textinput import TextInput
import os
import sys
import argparse
import time
from subprocess import PIPE, Popen, STDOUT
from threading import Thread
from Queue import Queue, Empty
import shutil
from lib.checklastblock import CheckLastBlock
from lib.savewallet import storeWallet
from lib.transferfunds import transferfundsrpccall
# Set POSIX argument for linux for something...
ON_POSIX = 'posix' in sys.builtin_module_names
# parser gets command line args
parser = argparse.ArgumentParser()
parser.parse_args()
# define functions to read output streams
def enqueue_output(out, queue):
for line in iter(out.readline, b''):
queue.put(line)
out.close()
def read_output(process, stdout, queue):
while True:
out = stdout.readline()
#print("Process is: "+process.poll())
if out == '' and process.poll() != None:
break
if out != '':
line = out.rstrip()
queue.put_nowait(line)
sys.stdout.flush()
# Function to split tx output lines
def txSplit(line):
split_line = line.split(': ')
try:
amount = float(split_line[1].split(',')[0])
tx = split_line[2].strip('<>')
date = split_line[0].split('.')[0]
return amount, tx, date
except ValueError:
return "Error", "Error", "Error"
except IndexError:
return "Error", "Error", "Error"
class MarkupLabel(Label):
""" Label class with markup on by default """
pass
class TabTextInput(TextInput):
""" Tab or enter to next TextInput class """
def __init__(self, *args, **kwargs):
self.next = kwargs.pop('next', None)
super(TabTextInput, self).__init__(*args, **kwargs)
def set_next(self, next):
self.next = next
def _keyboard_on_key_down(self, window, keycode, text, modifiers):
key, key_str = keycode
if key in (9, 13) and self.next is not None:
self.next.focus = True
self.next.select_all()
else:
super(TabTextInput, self)._keyboard_on_key_down(window, keycode, text, modifiers)
class InitialPopup(Popup):
""" Initial popup widget """
def loadWalletPopup(self):
self.dismiss()
lwp = LoadWalletPopup()
lwp.open()
def createWalletPopup(self):
self.dismiss()
cwp = CreateWalletPopup()
cwp.open()
def quit(self):
sys.exit(0)
class CreateWalletPopup(Popup):
""" Wallet creation screen popup """
wallet_name = ObjectProperty()
wallet_pw = ObjectProperty()
repeat_wallet_pw = ObjectProperty()
def createWallet(self):
electrum_coming = False
wallet_name = self.wallet_name.text
wallet_pw = self.wallet_pw.text
if self.wallet_pw.text == self.repeat_wallet_pw.text:
p = Popen(["simplewallet", "--generate-new-wallet",
"./" + wallet_name.replace('.', '') + "_walletData/" + wallet_name,
"--password", wallet_pw],
stdout=PIPE,
stdin=PIPE,
bufsize=1,
close_fds=ON_POSIX)
if not os.path.exists('./{0}_walletData'.format(wallet_name.replace('.', ''))):
os.mkdir('./{0}_walletData'.format(wallet_name.replace('.', '')))
else:
sys.exit(7777)
q = Queue()
t = Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True # thread dies with the program
t.start()
t_start = time.time()
while time.time() - t_start < 2:
try:
line = q.get_nowait() # or q.get(timeout=.1)
if "new wallet:" in line:
address = line.rstrip().split(': ')[1]
if "view key:" in line:
view_key = line.rstrip().split(': ')[1]
if 130 < len(line.rstrip()) < 350:
electrum = line.rstrip()
if "PLEASE NOTE:" in line:
electrum_coming = line.rstrip()
print("simplewallet output:", line.rstrip())
except Empty:
# print('no output yet')
pass
else: # got line
# print(line)
pass
# time.sleep(0.1)
p.kill()
# os.system("taskkill /im simplewallet.exe /f")
# print(p.pid)
with open('./{0}_walletData/info.txt'.format(wallet_name.replace('.', '')), 'w') as f:
f.write("Name:\n{0}\n\nAddress:\n{1}\n\nView key:\n{2}\n\nElectrum seed:\n{3}".format(
wallet_name, address, view_key, electrum))
with open("./CONFIG.file", 'w') as f:
f.write(wallet_name)
self.dismiss()
content = Button(
text="Your wallet has been created in the local directory {0}_walletData\nThe info.txt document in that folder contains your wallet recovery seed.\nPlease keep this seed safe, you can restore a corrupted/lost wallet with it.\nSomeone else can also bypass your password and steal your XMR with it.\nI suggest deleting or encrypting this file.".format(
wallet_name))
walletCreatedPopup = Popup(title="Wallet created window",
content=content)
content.bind(on_press=walletCreatedPopup.dismiss)
walletCreatedPopup.open()
App.get_running_app().root.launchWallet(wallet_name, wallet_pw)
else:
content = Button(text="Check passwords!!!\nClick to dismiss")
pwErrorPopup = Popup(title="Password match error window",
content=content)
content.bind(on_press=pwErrorPopup.dismiss)
pwErrorPopup.open()
class LoadWalletPopup(Popup):
""" Wallet loading screen popup """
wallet_path = ObjectProperty()
wallet_pw = ObjectProperty()
repeat_wallet_pw = ObjectProperty()
def loadWallet(self):
split_name = os.path.split(self.wallet_path.text)[1].split('.')
wallet_name = '.'.join(tuple(split_name[0:len(split_name) - 1]))
print(split_name, wallet_name)
wallet_pw = self.wallet_pw.text
wallet_error = False
if self.wallet_pw.text == self.repeat_wallet_pw.text:
p = Popen(["simplewallet", "--wallet-file",
self.wallet_path.text,
"--password", wallet_pw],
stdout=PIPE,
stdin=PIPE,
bufsize=1,
close_fds=ON_POSIX)
q = Queue()
t = Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True # thread dies with the program
t.start()
t_start = time.time()
while time.time() - t_start < 2:
try:
line = q.get_nowait() # or q.get(timeout=.1)
print("simplewallet output:", line.rstrip())
if "Opened wallet: " in line.rstrip():
address = line.rstrip().split(": ")[1]
if "Error: failed" in line.rstrip():
wallet_error = True
except Empty:
# print('no output yet')
pass
else: # got line
pass
p.kill()
if not wallet_error:
if not os.path.exists("./{0}_walletData".format(wallet_name.replace('.', ''))):
os.mkdir("./{0}_walletData".format(wallet_name.replace('.', '')))
with open("./CONFIG.file", 'w') as f:
f.write(wallet_name)
with open("./{0}_walletData/{1}.address.txt".format(wallet_name.replace('.', ''), wallet_name), 'w') as f:
f.write(address)
shutil.copyfile(self.wallet_path.text,
"./{0}_walletData/{1}.keys".format(wallet_name.replace('.', ''), wallet_name))
self.dismiss()
content = Button(
text="Your wallet keys file has been copied to the local directory {0}_walletData\nThe original has not been disturbed.".format(
wallet_name))
walletCopiedPopup = Popup(title="Wallet keys copied window",
content=content)
content.bind(on_press=walletCopiedPopup.dismiss)
walletCopiedPopup.open()
App.get_running_app().root.launchWallet(wallet_name, wallet_pw)
else:
content = Button(text="Wallet import error!\nClick to start over")
pwErrorPopup = Popup(title="Wallet import error window",
content=content)
content.bind(on_press=pwErrorPopup.dismiss)
ip = InitialPopup()
content.bind(on_press=ip.open)
pwErrorPopup.open()
class PasswordPopup(Popup):
""" Popup class to get pw on launch with CONFIG.file """
wallet_pw = ObjectProperty()
walletname_label = ObjectProperty()
def __init__(self, wallet_name):
super(PasswordPopup, self).__init__()
self.wallet_name = wallet_name
if not App.get_running_app().root.wallet_error:
self.walletname_label.text = "Please enter your password for {0} account:".format(self.wallet_name)
if App.get_running_app().root.wallet_error:
self.walletname_label.text = "There seems to be a problem with binary file,\nPlease enter your password to attempt rebuild of {0} account:".format(
self.wallet_name)
def launchWallet(self):
wallet_pw = self.wallet_pw.text
App.get_running_app().root.launchWallet(self.wallet_name, wallet_pw)
self.dismiss()
class TransferPopup(Popup):
""" Popup class to initiate XMR transfer """
transfer_popup_label = ObjectProperty()
def __init__(self, amount, address, mixin, paymentid):
super(TransferPopup, self).__init__()
self.amount = amount
self.address = address
self.mixin = mixin
self.paymentid = paymentid
self.popuptext = "Are you sure you want to send {0:0.9f} XMR\n to [size=8]{1}[/size]\n with mixin count {2} and payment id [size=8]{3}[/size]".format(
float(amount), address, mixin, paymentid)
self.transfer_popup_label.text = self.popuptext
def transfer(self):
txid = transferfundsrpccall(self.amount, self.address, self.mixin, self.paymentid)
txid_popup = TxIDPopup(txid)
txid_popup.open()
class TxIDPopup(Popup):
txid_label = ObjectProperty()
def __init__(self, txid):
super(TxIDPopup, self).__init__()
self.txid = txid
self.txid_label.text = "TxID: [ref=txid]{0}[/ref]\n(Click TxID to copy to clipboard)".format(txid)
def selecttxid(self):
Clipboard.put(self.txid)
class RootWidget(Accordion):
"""Root Kivy accordion widget class"""
wallet_error = None
# Transfer XMR id items
address_input_textinput = ObjectProperty()
amount_input_textinput = ObjectProperty()
mixin_input_textinput = ObjectProperty()
paymentid_input_textinput = ObjectProperty()
# Wallet account id items
wallet_account_label = ObjectProperty()
wallet_address_label = ObjectProperty()
unlocked_balance_label = ObjectProperty()
total_balance_label = ObjectProperty()
calculated_balance_label = ObjectProperty()
# Daemon id items
daemon_server_textinput = ObjectProperty()
blockheight_label = ObjectProperty()
last_reward_label = ObjectProperty()
last_block_time_label = ObjectProperty()
time_since_last_label = ObjectProperty()
wallet_block_label = ObjectProperty()
blockheight = 0
# Process, queue, and thread variables
daemon_queue = Queue()
daemon_thread = None
wallet_process = None
wallet_queue = Queue()
wallet_thread = None
# Other variables
calculated_balance = 0.0
total_balance = 0.0
unlocked_balance = 0.0
temp_tx_amount = 0.0
temp_tx_hash = ""
temp_tx_paymentid = ""
wallet_save_time = None
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.daemon_thread = Thread(target=self.checkDaemonStatus,
args=(self.daemon_queue, self.daemon_server_textinput.text))
self.daemon_thread.daemon = True
self.daemon_thread.start()
if os.path.isfile('./CONFIG.file'):
with open('./CONFIG.file', 'r') as f:
self.wallet_name = f.read().rstrip()
print(self.wallet_name)
Clock.schedule_once(self.initialPassword, 0.5)
else:
Clock.schedule_once(self.initialConfig, 0.5)
Clock.schedule_interval(self.checkDaemon, 5)
def initialConfig(self, dt):
ip = InitialPopup()
ip.open()
def initialPassword(self, dt):
gp = PasswordPopup(self.wallet_name)
gp.open()
def checkDaemonStatus(self, daemon_queue, server):
"""Class to be threaded for checking bitmonerod status"""
# print(server)
height, reward, timesince, localtime = CheckLastBlock(server + "/json_rpc")
daemon_queue.put((height, reward, timesince, localtime))
# print("sleeping")
time.sleep(1)
self.checkDaemonStatus(daemon_queue, self.daemon_server_textinput.text)
def checkDaemon(self, dt):
try:
height, reward, timesince, localtime = self.daemon_queue.get_nowait()
self.blockheight = int(height)
self.blockheight_label.text = "[b][color=00ffff]{0}[/b][/color]".format(height)
self.last_reward_label.text = "[b][color=00ffff]{0:.2f}[/b][/color] XMR".format(reward * 1e-12)
self.last_block_time_label.text = "[b][color=00ffff]{0}[/b][/color]".format(localtime)
self.time_since_last_label.text = "[b][color=00ffff]{0:.0f}[/b][/color] seconds".format(timesince)
except:
pass
def launchWallet(self, wallet_name, wallet_pw):
with open("./{0}_walletData/{1}.address.txt".format(wallet_name.replace('.', ''), wallet_name), 'r') as f:
self.address = f.read().rstrip()
self.wallet_account_label.text = "[b][color=00ffff]{0}[/b][/color]".format(wallet_name)
self.wallet_address_label.text = "[b][color=00ffff][ref=Address]{0}[/ref][/b][/color]".format(self.address)
if not os.path.isfile("./{0}_walletData/{1}".format(wallet_name.replace('.', ''), wallet_name)):
self.wallet_process = Popen(["simplewallet.exe", "--wallet-file", "./{0}_walletData/{1}".format(
wallet_name.replace('.', ''), wallet_name), "--password", wallet_pw],
stdin=PIPE,
stdout=PIPE,
stderr=STDOUT,
bufsize=1)
self.wallet_process.communicate(input="exit\r\n")
self.wallet_process.kill()
# try:
# system("taskkill /im simplewallet.exe /f")
# except:
# print("Simplewallet.exe not running")
if os.path.isfile("./{0}_walletData/tx.txt".format(wallet_name.replace('.', ''))):
with open("./{0}_walletData/tx.txt".format(wallet_name.replace('.', ''), 'r')) as previous_txs:
txs = previous_txs.read().split('\n')
for idx, line in enumerate(txs):
this_amount, this_tx, this_date = txSplit(line)
if idx < len(txs) - 1:
next_amount, next_tx, date = txSplit(txs[idx+1])
else:
next_amount, next_tx, date = "Done", "Done", "Done"
if this_tx != next_tx and self.temp_tx_amount == 0.0:
if "Received" in line:
l = Label(text="Received {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx),
size_hint_y=None, height=15, font_size=9)
self.ids.tx_list.add_widget(l)
self.calculated_balance += this_amount
elif "Spent" in line:
l = Label(text="Spent {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx),
size_hint_y=None, height=15, font_size=9)
self.ids.tx_list.add_widget(l)
self.calculated_balance -= this_amount
elif this_tx == next_tx and self.temp_tx_amount == 0.0:
self.temp_tx_amount += this_amount + next_amount
elif this_tx == next_tx and self.temp_tx_amount != 0.0:
self.temp_tx_amount += next_amount
elif this_tx != next_tx and self.temp_tx_amount != 0.0:
if "Received" in line:
l = Label(text="Received {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx),
size_hint_y=None, height=15, font_size=9)
self.ids.tx_list.add_widget(l)
self.calculated_balance += self.temp_tx_amount
elif "Spent" in line:
l = Label(text="Spent {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx),
size_hint_y=None, height=15, font_size=9)
self.ids.tx_list.add_widget(l)
self.calculated_balance -= self.temp_tx_amount
self.temp_tx_amount = 0.0
self.temp_tx_amount = 0.0
self.calculated_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color]".format(self.calculated_balance)
self.wallet_process = Popen(["simplewallet", "--wallet-file", "./{0}_walletData/{1}.keys".format(
wallet_name.replace('.', ''), wallet_name), "--password", wallet_pw,
"--daemon-address", self.daemon_server_textinput.text, "--rpc-bind-port", "19091"],
stdin=PIPE,
stdout=PIPE,
stderr=STDOUT,
bufsize=1)
self.wallet_thread = Thread(target=read_output,
args=(self.wallet_process, self.wallet_process.stdout, self.wallet_queue))
# self.wallet_thread = Thread(target=enqueue_output,
# args=(self.wallet_process.stdout, self.wallet_queue))
self.wallet_thread.daemon = True # thread dies with the program
self.wallet_thread.start()
Clock.schedule_interval(self.readWalletQueue, .01)
# Clock.schedule_interval(self.saveWallet, 60)
def readWalletQueue(self, dt):
print(self.wallet_thread.is_alive)
try:
print("trying")
newLine = self.wallet_queue.get_nowait()
# with open("rpcwallet.log", 'a') as logfile:
# logfile.write(newLine+'\n')
print newLine
if "Wallet initialize failed: failed to read file" in newLine:
self.wallet_thread.join()
self.wallet_error = True
os.remove("./{0}_walletData/{1}".format(self.wallet_name.replace('.', ''), self.wallet_name))
Clock.schedule_once(self.initialPassword, 0.5)
if "height:" in newLine:
height = newLine.split(", ")[1].split(": ")[1]
self.wallet_block_label.text = "[b][color=00ffff]{0}[/b][/color] of [b][color=00ffff]{1}[/b][/color]".format(
height, self.blockheight)
elif "height " in newLine:
height = newLine.split(", ")[1].split(" ")[1]
self.wallet_block_label.text = "[b][color=00ffff]{0}[/b][/color] of [b][color=00ffff]{1}[/b][/color]".format(
height, self.blockheight)
elif "Refresh done," in newLine:
print("refresh done")
split_line = newLine.split(',')
self.total_balance = float(split_line[2].split(': ')[1])
self.unlocked_balance = float(split_line[3].split(': ')[1])
self.total_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color]".format(self.total_balance)
self.unlocked_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color]".format(self.unlocked_balance)
if not self.wallet_save_time:
print("First save launching!")
Clock.schedule_once(self.saveWallet, 0.1)
self.wallet_save_time = time.time()
print("Wallet saved at: {0}".format(self.wallet_save_time))
if self.wallet_save_time:
if time.time() - self.wallet_save_time > 1800:
Clock.schedule_once(self.saveWallet, 0.1)
print(time.time(), self.wallet_save_time, "saving")
self.wallet_save_time = time.time()
elif "Received money" in newLine and "with tx" in newLine:
with open("./{0}_walletData/tx.txt".format(self.wallet_name), 'a') as tx_file:
this_amount, this_tx, this_date = txSplit(newLine)
tx_file.write(newLine + '\n')
self.calculated_balance += this_amount
self.calculated_balance_label.text = "[b][color=00ffff]{0:.3f}[/b][/color]".format(
self.calculated_balance)
l = Label(text="Received {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx),
size_hint_y=None, height=15, font_size=9)
self.ids.tx_list.add_widget(l)
elif "Spent money" in newLine and "with tx" in newLine:
with open("./{0}_walletData/tx.txt".format(self.wallet_name), 'a') as tx_file:
this_amount, this_tx, this_date = txSplit(newLine)
tx_file.write(newLine + '\n')
self.calculated_balance -= this_amount
l = Label(text="Spent {0:0.3f} XMR on {1} in tx: {2}".format(this_amount, this_date, this_tx),
size_hint_y=None, height=15, font_size=9)
self.ids.tx_list.add_widget(l)
# elif "Payment found" in newLine:
# with open("./{0}_walletData/tx.txt".format(self.wallet_name), 'a') as tx_file:
# tx_file.write(newLine + '\n')
except Empty:
pass
# if not self.wallet_thread.is_alive():
# print("dead")
# self.wallet_thread.start()
# Clock.schedule_interval(self.readWalletQueue, 0.01)
def saveWallet(self, dt):
print("Launching save thread")
save_thread = Thread(target=storeWallet)
save_thread.daemon = True
save_thread.start()
save_thread.join()
print("Finished save thread")
def selectAddress(self):
"""copy address to clipboard when user clicks"""
print('User clicked on ', self.address)
self.daemon_server_textinput.copy(self.address)
def transferfunds(self):
"""initiate transfer of funds to new address"""
try:
float(self.amount_input_textinput.text)
tp = TransferPopup(self.amount_input_textinput.text, self.address_input_textinput.text,
self.mixin_input_textinput.text, self.paymentid_input_textinput.text)
tp.open()
except ValueError:
print("no,no")
class KVNewApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
Config.set('kivy', 'exit_on_escape', 0)
Config.set('kivy', 'window_icon', "./lilicon.ico")
KVNewApp().run()
try:
App.get_running_app().root.wallet_process.kill()
except:
print("Wallet process not running")
# try:
# system("taskkill /im simplewallet.exe /f")
# except:
# print("Simplewallet.exe not running")