|
|
@ -35,30 +35,20 @@ class InstallWizard(Widget): |
|
|
|
self.config = config |
|
|
|
self.network = network |
|
|
|
self.storage = storage |
|
|
|
self.wallet = Wallet(self.storage) |
|
|
|
|
|
|
|
def waiting_dialog(self, task, |
|
|
|
msg= _("Electrum is generating your addresses," |
|
|
|
" please wait."), |
|
|
|
on_complete=None): |
|
|
|
def waiting_dialog(self, task, msg): |
|
|
|
'''Perform a blocking task in the background by running the passed |
|
|
|
method in a thread. |
|
|
|
''' |
|
|
|
|
|
|
|
def target(): |
|
|
|
|
|
|
|
# run your threaded function |
|
|
|
try: |
|
|
|
task() |
|
|
|
except Exception as err: |
|
|
|
Clock.schedule_once(lambda dt: app.show_error(str(err))) |
|
|
|
|
|
|
|
# on completion hide message |
|
|
|
Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1) |
|
|
|
|
|
|
|
# call completion routine |
|
|
|
if on_complete: |
|
|
|
Clock.schedule_once(lambda dt: on_complete()) |
|
|
|
|
|
|
|
app.show_info_bubble( |
|
|
|
text=msg, icon='atlas://gui/kivy/theming/light/important', |
|
|
|
pos=Window.center, width='200sp', arrow_pos=None, modal=True) |
|
|
@ -72,91 +62,77 @@ class InstallWizard(Widget): |
|
|
|
|
|
|
|
def is_any(self, seed_e): |
|
|
|
text = self.get_seed_text(seed_e) |
|
|
|
return (Wallet.is_seed(text) or |
|
|
|
Wallet.is_old_mpk(text) or |
|
|
|
Wallet.is_xpub(text) or |
|
|
|
Wallet.is_xprv(text) or |
|
|
|
Wallet.is_address(text) or |
|
|
|
Wallet.is_private_key(text)) |
|
|
|
return Wallet.is_any(text) |
|
|
|
|
|
|
|
def run(self, action): |
|
|
|
'''Entry point of our Installation wizard |
|
|
|
''' |
|
|
|
def run(self, action, *args): |
|
|
|
'''Entry point of our Installation wizard''' |
|
|
|
if not action: |
|
|
|
return |
|
|
|
|
|
|
|
Factory.CreateRestoreDialog( |
|
|
|
on_release=self.on_creatrestore_complete, |
|
|
|
action=action).open() |
|
|
|
|
|
|
|
def on_creatrestore_complete(self, dialog, button): |
|
|
|
if not button: |
|
|
|
# soft back or escape button pressed |
|
|
|
return self.dispatch('on_wizard_complete', None) |
|
|
|
dialog.close() |
|
|
|
|
|
|
|
action = dialog.action |
|
|
|
if button == dialog.ids.create: |
|
|
|
wallet = Wallet(self.storage) |
|
|
|
self.password_dialog(wallet=wallet, mode='create') |
|
|
|
|
|
|
|
elif button == dialog.ids.restore: |
|
|
|
wallet = None |
|
|
|
self.restore_seed_dialog(wallet) |
|
|
|
|
|
|
|
if action == 'new': |
|
|
|
self.new() |
|
|
|
elif action == 'create': |
|
|
|
self.create() |
|
|
|
elif action == 'restore': |
|
|
|
self.restore() |
|
|
|
elif action == 'enter_pin': |
|
|
|
self.enter_pin(*args) |
|
|
|
elif action == 'confirm_pin': |
|
|
|
self.confirm_pin(*args) |
|
|
|
elif action == 'add_seed': |
|
|
|
self.add_seed(*args) |
|
|
|
elif action == 'terminate': |
|
|
|
self.terminate() |
|
|
|
else: |
|
|
|
self.dispatch('on_wizard_complete', None) |
|
|
|
raise BaseException("unknown action", action) |
|
|
|
|
|
|
|
def new(self): |
|
|
|
def on_release(dialog, button): |
|
|
|
if not button: |
|
|
|
# soft back or escape button pressed |
|
|
|
return self.dispatch('on_wizard_complete', None) |
|
|
|
dialog.close() |
|
|
|
action = dialog.action |
|
|
|
if button == dialog.ids.create: |
|
|
|
self.run('create') |
|
|
|
elif button == dialog.ids.restore: |
|
|
|
self.run('restore') |
|
|
|
else: |
|
|
|
self.dispatch('on_wizard_complete', None) |
|
|
|
Factory.CreateRestoreDialog(on_release=on_release).open() |
|
|
|
|
|
|
|
def restore_seed_dialog(self, wallet): |
|
|
|
from electrum_gui.kivy.uix.dialogs.create_restore import\ |
|
|
|
RestoreSeedDialog |
|
|
|
def restore(self): |
|
|
|
from create_restore import RestoreSeedDialog |
|
|
|
def on_seed(_dlg, btn): |
|
|
|
if btn is _dlg.ids.back: |
|
|
|
_dlg.close() |
|
|
|
self.run('new') |
|
|
|
return |
|
|
|
text = self.get_seed_text(_dlg.ids.text_input_seed) |
|
|
|
need_password = Wallet.should_encrypt(text) |
|
|
|
_dlg.close() |
|
|
|
if need_password: |
|
|
|
self.run('enter_pin', text) |
|
|
|
else: |
|
|
|
self.wallet = Wallet.from_text(text) |
|
|
|
# fixme: sync |
|
|
|
RestoreSeedDialog( |
|
|
|
on_release=partial(self.on_verify_restore_ok, wallet), |
|
|
|
on_release=partial(on_seed), |
|
|
|
wizard=weakref.proxy(self)).open() |
|
|
|
|
|
|
|
|
|
|
|
def on_verify_restore_ok(self, wallet, _dlg, btn, restore=False): |
|
|
|
if btn in (_dlg.ids.back, _dlg.ids.but_close) : |
|
|
|
_dlg.close() |
|
|
|
Factory.CreateRestoreDialog( |
|
|
|
on_release=self.on_creatrestore_complete).open() |
|
|
|
return |
|
|
|
|
|
|
|
seed = self.get_seed_text(_dlg.ids.text_input_seed) |
|
|
|
if not seed: |
|
|
|
return app.show_error(_("No seed!"), duration=.5) |
|
|
|
|
|
|
|
_dlg.close() |
|
|
|
|
|
|
|
if Wallet.is_seed(seed): |
|
|
|
return self.password_dialog(wallet=wallet, mode='restore', |
|
|
|
seed=seed) |
|
|
|
elif Wallet.is_xpub(seed): |
|
|
|
wallet = Wallet.from_xpub(seed, self.storage) |
|
|
|
elif Wallet.is_address(seed): |
|
|
|
wallet = Wallet.from_address(seed, self.storage) |
|
|
|
elif Wallet.is_private_key(seed): |
|
|
|
wallet = Wallet.from_private_key(seed, self.storage) |
|
|
|
else: |
|
|
|
return app.show_error(_('Not a valid seed. App will now exit'), |
|
|
|
exit=True, modal=True, duration=.5) |
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
def show_seed(self, wallet=None, instance=None, password=None, |
|
|
|
wallet_name=None, mode='create', seed=''): |
|
|
|
if instance and (not wallet or not wallet.seed): |
|
|
|
return app.show_error(_('No seed')) |
|
|
|
|
|
|
|
if not seed: |
|
|
|
try: |
|
|
|
seed = self.wallet.get_seed(password) |
|
|
|
except Exception: |
|
|
|
return app.show_error(_('Incorrect Password')) |
|
|
|
|
|
|
|
brainwallet = seed |
|
|
|
|
|
|
|
msg2 = _("[color=#414141]"+\ |
|
|
|
def add_seed(self, seed, password): |
|
|
|
def task(): |
|
|
|
self.wallet.add_seed(seed, password) |
|
|
|
self.wallet.create_master_keys(password) |
|
|
|
self.wallet.create_main_account() |
|
|
|
self.wallet.synchronize() |
|
|
|
self.run('terminate') |
|
|
|
msg= _("Electrum is generating your addresses, please wait.") |
|
|
|
self.waiting_dialog(task, msg) |
|
|
|
|
|
|
|
def create(self): |
|
|
|
from create_restore import InitSeedDialog |
|
|
|
seed = self.wallet.make_seed() |
|
|
|
msg = _("[color=#414141]"+\ |
|
|
|
"[b]PLEASE WRITE DOWN YOUR SEED PASS[/b][/color]"+\ |
|
|
|
"[size=9]\n\n[/size]" +\ |
|
|
|
"[color=#929292]If you ever forget your pincode, your seed" +\ |
|
|
@ -164,195 +140,40 @@ class InstallWizard(Widget): |
|
|
|
"[b]only way to recover[/b][/color] your wallet. Your " +\ |
|
|
|
" [color=#EB984E][b]Bitcoins[/b][/color] will otherwise be" +\ |
|
|
|
" [color=#EB984E][b]lost forever![/b][/color]") |
|
|
|
|
|
|
|
if wallet.imported_keys: |
|
|
|
msg2 += "[b][color=#ff0000ff]" + _("WARNING") + "[/color]:[/b] " +\ |
|
|
|
_("Your wallet contains imported keys. These keys cannot" +\ |
|
|
|
" be recovered from seed.") |
|
|
|
|
|
|
|
def on_ok_press(_dlg, _btn): |
|
|
|
def on_ok(_dlg, _btn): |
|
|
|
_dlg.close() |
|
|
|
mode = _dlg.mode |
|
|
|
if _btn != _dlg.ids.confirm: |
|
|
|
if not instance: |
|
|
|
self.password_dialog(wallet, mode=mode) |
|
|
|
return |
|
|
|
# confirm |
|
|
|
if instance is None: |
|
|
|
# in initial phase create mode |
|
|
|
# save seed with password |
|
|
|
|
|
|
|
def create(password): |
|
|
|
wallet.add_seed(seed, password) |
|
|
|
wallet.create_master_keys(password) |
|
|
|
wallet.create_main_account() |
|
|
|
wallet.synchronize() # generate first addresses offline |
|
|
|
|
|
|
|
self.waiting_dialog(partial(create, password), |
|
|
|
on_complete=partial(self.load_network, |
|
|
|
wallet, mode=mode)) |
|
|
|
|
|
|
|
|
|
|
|
from electrum_gui.kivy.uix.dialogs.create_restore import InitSeedDialog |
|
|
|
InitSeedDialog(message=msg2, |
|
|
|
seed_msg=brainwallet, on_release=on_ok_press, mode=mode).open() |
|
|
|
|
|
|
|
def password_dialog(self, wallet=None, instance=None, mode='create', |
|
|
|
seed=''): |
|
|
|
"""Can be called directly (instance is None) |
|
|
|
or from a callback (instance is not None)""" |
|
|
|
app = App.get_running_app() |
|
|
|
|
|
|
|
if mode != 'create' and wallet and wallet.is_watching_only(): |
|
|
|
return app.show_error('This is a watching only wallet') |
|
|
|
|
|
|
|
if instance and not wallet.seed: |
|
|
|
return app.show_error('No seed !!', exit=True, modal=True) |
|
|
|
|
|
|
|
if instance is not None: |
|
|
|
if wallet.use_encryption: |
|
|
|
msg = ( |
|
|
|
_('Your wallet is encrypted. Use this dialog to change" + \ |
|
|
|
" your password.') + '\n' + _('To disable wallet" + \ |
|
|
|
" encryption, enter an empty new password.')) |
|
|
|
mode = 'confirm' |
|
|
|
if _btn == _dlg.ids.confirm: |
|
|
|
self.run('enter_pin', seed) |
|
|
|
else: |
|
|
|
msg = _('Your wallet keys are not encrypted') |
|
|
|
mode = 'new' |
|
|
|
else: |
|
|
|
msg = _("Please choose a password to encrypt your wallet keys.") +\ |
|
|
|
'\n' + _("Leave these fields empty if you want to disable" + \ |
|
|
|
" encryption.") |
|
|
|
|
|
|
|
def on_release(wallet, seed, _dlg, _btn): |
|
|
|
ti_password = _dlg.ids.ti_password |
|
|
|
ti_new_password = _dlg.ids.ti_new_password |
|
|
|
ti_confirm_password = _dlg.ids.ti_confirm_password |
|
|
|
if _btn != _dlg.ids.next: |
|
|
|
if mode == 'restore': |
|
|
|
# back is disabled cause seed is already set |
|
|
|
return |
|
|
|
_dlg.close() |
|
|
|
if not instance: |
|
|
|
# back on create |
|
|
|
Factory.CreateRestoreDialog( |
|
|
|
on_release=self.on_creatrestore_complete).open() |
|
|
|
return |
|
|
|
|
|
|
|
# Confirm |
|
|
|
wallet_name = _dlg.ids.ti_wallet_name.text |
|
|
|
new_password = unicode(ti_new_password.text) |
|
|
|
new_password2 = unicode(ti_confirm_password.text) |
|
|
|
|
|
|
|
if new_password != new_password2: |
|
|
|
# passwords don't match |
|
|
|
ti_password.text = "" |
|
|
|
ti_new_password.text = "" |
|
|
|
ti_confirm_password.text = "" |
|
|
|
if ti_password.disabled: |
|
|
|
ti_new_password.focus = True |
|
|
|
else: |
|
|
|
ti_password.focus = True |
|
|
|
return app.show_error(_('Passwords do not match'), duration=.5) |
|
|
|
|
|
|
|
if not new_password: |
|
|
|
new_password = None |
|
|
|
|
|
|
|
if mode == 'restore': |
|
|
|
password = unicode(ti_password.text) |
|
|
|
# if wallet and wallet.use_encryption else |
|
|
|
# None) |
|
|
|
if not password: |
|
|
|
password = None |
|
|
|
wallet = Wallet.from_text(seed, password, self.storage) |
|
|
|
|
|
|
|
def on_complete(*l): |
|
|
|
self.load_network(wallet, mode='restore') |
|
|
|
_dlg.close() |
|
|
|
|
|
|
|
self.waiting_dialog(wallet.synchronize, |
|
|
|
msg=_("generating addresses"), |
|
|
|
on_complete=on_complete) |
|
|
|
return |
|
|
|
|
|
|
|
if not instance: |
|
|
|
# create mode |
|
|
|
_dlg.close() |
|
|
|
seed = wallet.make_seed() |
|
|
|
|
|
|
|
return self.show_seed(password=new_password, wallet=wallet, |
|
|
|
wallet_name=wallet_name, mode=mode, |
|
|
|
seed=seed) |
|
|
|
|
|
|
|
# change password mode |
|
|
|
try: |
|
|
|
seed = wallet.decode_seed(password) |
|
|
|
except BaseException: |
|
|
|
return app.show_error(_('Incorrect Password'), duration=.5) |
|
|
|
|
|
|
|
# test carefully |
|
|
|
try: |
|
|
|
wallet.update_password(seed, password, new_password) |
|
|
|
except BaseException: |
|
|
|
return app.show_error(_('Failed to update password'), exit=True) |
|
|
|
else: |
|
|
|
app.show_info_bubble( |
|
|
|
text=_('Password successfully updated'), duration=1, |
|
|
|
pos=_btn.pos) |
|
|
|
_dlg.close() |
|
|
|
|
|
|
|
if instance is None: # in initial phase |
|
|
|
self.load_wallet() |
|
|
|
self.app.update_wallet() |
|
|
|
|
|
|
|
from electrum_gui.kivy.uix.dialogs.create_restore import ChangePasswordDialog |
|
|
|
cpd = ChangePasswordDialog( |
|
|
|
message=msg, |
|
|
|
mode=mode, |
|
|
|
on_release=partial(on_release, |
|
|
|
wallet, seed)).open() |
|
|
|
|
|
|
|
def load_network(self, wallet, mode='create'): |
|
|
|
#if not self.config.get('server'): |
|
|
|
if self.network: |
|
|
|
if self.network.interfaces: |
|
|
|
if mode not in ('restore', 'create'): |
|
|
|
self.network_dialog() |
|
|
|
self.run('new') |
|
|
|
InitSeedDialog(message=msg, seed_msg=seed, on_release=on_ok, mode='create').open() |
|
|
|
|
|
|
|
def enter_pin(self, seed): |
|
|
|
from password_dialog import PasswordDialog |
|
|
|
def callback(pin): |
|
|
|
self.run('confirm_pin', seed, pin) |
|
|
|
popup = PasswordDialog('Enter PIN', callback) |
|
|
|
popup.open() |
|
|
|
|
|
|
|
def confirm_pin(self, seed, pin): |
|
|
|
from password_dialog import PasswordDialog |
|
|
|
def callback(conf): |
|
|
|
if conf == pin: |
|
|
|
self.run('add_seed', seed, pin) |
|
|
|
else: |
|
|
|
app.show_error(_('You are offline')) |
|
|
|
self.network.stop() |
|
|
|
self.network = None |
|
|
|
app = App.get_running_app() |
|
|
|
app.show_error(_('Passwords do not match'), duration=.5) |
|
|
|
popup = PasswordDialog('Confirm PIN', callback) |
|
|
|
popup.open() |
|
|
|
|
|
|
|
if mode in ('restore', 'create'): |
|
|
|
# auto cycle |
|
|
|
self.config.set_key('auto_cycle', True, True) |
|
|
|
|
|
|
|
# start wallet threads |
|
|
|
wallet.start_threads(self.network) |
|
|
|
|
|
|
|
if not mode == 'restore': |
|
|
|
return self.dispatch('on_wizard_complete', wallet) |
|
|
|
|
|
|
|
def get_text(text): |
|
|
|
def set_text(*l): app.info_bubble.ids.lbl.text=text |
|
|
|
Clock.schedule_once(set_text) |
|
|
|
|
|
|
|
def on_complete(*l): |
|
|
|
if not self.network: |
|
|
|
app.show_info( |
|
|
|
_("This wallet was restored offline. It may contain more" |
|
|
|
" addresses than displayed."), duration=.5) |
|
|
|
return self.dispatch('on_wizard_complete', wallet) |
|
|
|
|
|
|
|
if wallet.is_found(): |
|
|
|
app.show_info(_("Recovery successful"), duration=.5) |
|
|
|
else: |
|
|
|
app.show_info(_("No transactions found for this seed"), |
|
|
|
duration=.5) |
|
|
|
return self.dispatch('on_wizard_complete', wallet) |
|
|
|
|
|
|
|
self.waiting_dialog(lambda: wallet.restore(get_text), |
|
|
|
on_complete=on_complete) |
|
|
|
def terminate(self): |
|
|
|
self.wallet.start_threads(self.network) |
|
|
|
app.load_wallet(self.wallet) |
|
|
|
self.dispatch('on_wizard_complete', wallet) |
|
|
|
|
|
|
|
def on_wizard_complete(self, wallet): |
|
|
|
pass |
|
|
|
if wallet.is_found(): |
|
|
|
app.show_info(_("Recovery successful"), duration=.5) |
|
|
|
else: |
|
|
|
app.show_info(_("No transactions found for this seed"), |
|
|
|
duration=.5) |
|
|
|