Fix merge conflict
This commit is contained in:
commit
dfd48319a3
|
@ -5,3 +5,4 @@ build/
|
|||
dist/
|
||||
*.egg/
|
||||
/electrum.py
|
||||
contrib/pyinstaller/
|
||||
|
|
|
@ -22,3 +22,5 @@ include scripts/servers
|
|||
include scripts/validate_tx
|
||||
include scripts/watch_address
|
||||
recursive-include data *
|
||||
recursive-include locale *.mo
|
||||
recursive-include docs *
|
||||
|
|
|
@ -35,8 +35,7 @@ For an example, see Gavin's tutorial: https://gist.github.com/gavinandresen/3966
|
|||
1. user creates an unsigned transaction using the online (watching-only) wallet.
|
||||
2. unsigned transaction is copied to the offline computer, and signed by the offline wallet.
|
||||
3. signed transaction is copied to the online computer, broadcasted by the online client.
|
||||
|
||||
* Raw transactions can also be loaded/signed/broadcasted via the GUI.
|
||||
4. All these steps can be done via the command line interface or the classic GUI.
|
||||
|
||||
* Many command line commands have been renamed in order to make the syntax consistent with bitcoind.
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w --onefile "C:/ele
|
|||
$PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec
|
||||
|
||||
# For building NSIS installer, run:
|
||||
wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsis
|
||||
wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsi
|
||||
#wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis
|
||||
|
||||
DATE=`date +"%Y%m%d"`
|
||||
|
|
|
@ -40,7 +40,7 @@ $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w --onefile "C:/ele
|
|||
$PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec
|
||||
|
||||
# For building NSIS installer, run:
|
||||
wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsis
|
||||
wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsi
|
||||
#wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis
|
||||
|
||||
cd dist
|
||||
|
|
|
@ -1,24 +1,64 @@
|
|||
# -*- mode: python -*-
|
||||
a = Analysis(['C:/electrum/electrum'],
|
||||
pathex=['Z:\\electrum-wine'],
|
||||
hiddenimports=[],
|
||||
excludes=['Tkinter'],
|
||||
|
||||
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
|
||||
a = Analysis(['electrum', 'gui/gui_classic.py', 'gui/gui_lite.py', 'gui/gui_text.py',
|
||||
'lib/util.py', 'lib/wallet.py', 'lib/simple_config.py',
|
||||
'lib/bitcoin.py', 'lib/deserialize.py'
|
||||
],
|
||||
hiddenimports=["lib","gui"],
|
||||
pathex=['lib:gui:plugins'],
|
||||
hookspath=None)
|
||||
pyz = PYZ(a.pure, level=0)
|
||||
|
||||
##### include mydir in distribution #######
|
||||
def extra_datas(mydir):
|
||||
def rec_glob(p, files):
|
||||
import os
|
||||
import glob
|
||||
for d in glob.glob(p):
|
||||
if os.path.isfile(d):
|
||||
files.append(d)
|
||||
rec_glob("%s/*" % d, files)
|
||||
files = []
|
||||
rec_glob("%s/*" % mydir, files)
|
||||
extra_datas = []
|
||||
for f in files:
|
||||
extra_datas.append((f, f, 'DATA'))
|
||||
|
||||
return extra_datas
|
||||
###########################################
|
||||
|
||||
# append dirs
|
||||
|
||||
# Theme data
|
||||
a.datas += extra_datas('data')
|
||||
|
||||
# Localization
|
||||
a.datas += extra_datas('locale')
|
||||
|
||||
# Py folders that are needed because of the magic import finding
|
||||
a.datas += extra_datas('gui')
|
||||
a.datas += extra_datas('lib')
|
||||
a.datas += extra_datas('plugins')
|
||||
|
||||
pyz = PYZ(a.pure)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=1,
|
||||
name=os.path.join('build\\pyi.win32\\electrum', 'electrum.exe'),
|
||||
debug=False,
|
||||
debug=True,
|
||||
strip=None,
|
||||
upx=True,
|
||||
console=False )
|
||||
upx=False,
|
||||
icon='icons/electrum.ico',
|
||||
console=True)
|
||||
# The console True makes an annoying black box pop up, but it does make Electrum accept command line options.
|
||||
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=None,
|
||||
upx=True,
|
||||
debug=False,
|
||||
icon='icons/electrum.ico',
|
||||
console=True,
|
||||
name=os.path.join('dist', 'electrum'))
|
||||
app = BUNDLE(coll,
|
||||
name=os.path.join('dist', 'electrum.app'))
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
;--------------------------------
|
||||
;Pages
|
||||
|
||||
!insertmacro MUI_PAGE_LICENSE "tmp/LICENCE"
|
||||
;!insertmacro MUI_PAGE_LICENSE "tmp/LICENCE"
|
||||
;!insertmacro MUI_PAGE_COMPONENTS
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
PYTHON_URL=http://www.python.org/ftp/python/2.6.6/python-2.6.6.msi
|
||||
PYQT4_URL=http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.9.5/PyQt-Py2.6-x86-gpl-4.9.5-1.exe
|
||||
PYWIN32_URL=http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.6.exe/download
|
||||
PYINSTALLER_URL=https://github.com/downloads/pyinstaller/pyinstaller/pyinstaller-2.0.zip
|
||||
PYINSTALLER_URL=http://downloads.sourceforge.net/project/pyinstaller/2.0/pyinstaller-2.0.zip
|
||||
NSIS_URL=http://prdownloads.sourceforge.net/nsis/nsis-2.46-setup.exe?download
|
||||
#ZBAR_URL=http://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download
|
||||
|
||||
|
|
33
electrum
33
electrum
|
@ -34,20 +34,20 @@ except ImportError:
|
|||
sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
|
||||
|
||||
|
||||
is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd()
|
||||
is_android = 'ANDROID_DATA' in os.environ
|
||||
|
||||
# load local module as electrum
|
||||
if os.path.exists("lib") or is_android:
|
||||
import imp
|
||||
fp, pathname, description = imp.find_module('lib')
|
||||
imp.load_module('electrum', fp, pathname, description)
|
||||
fp, pathname, description = imp.find_module('gui')
|
||||
imp.load_module('electrum_gui', fp, pathname, description)
|
||||
import __builtin__
|
||||
__builtin__.use_local_modules = is_local or is_android
|
||||
|
||||
# load local module as electrum
|
||||
if __builtin__.use_local_modules:
|
||||
import imp
|
||||
imp.load_module('electrum', *imp.find_module('lib'))
|
||||
imp.load_module('electrum_gui', *imp.find_module('gui'))
|
||||
|
||||
from electrum import *
|
||||
|
||||
|
||||
# get password routine
|
||||
def prompt_password(prompt, confirm=True):
|
||||
import getpass
|
||||
|
@ -162,6 +162,7 @@ if __name__ == '__main__':
|
|||
gui.show_seed()
|
||||
|
||||
verifier = WalletVerifier(interface, config)
|
||||
verifier.start()
|
||||
wallet.set_verifier(verifier)
|
||||
synchronizer = WalletSynchronizer(wallet, config)
|
||||
synchronizer.start()
|
||||
|
@ -181,7 +182,6 @@ if __name__ == '__main__':
|
|||
gui.password_dialog()
|
||||
|
||||
wallet.save()
|
||||
verifier.start()
|
||||
gui.main(url)
|
||||
wallet.save()
|
||||
|
||||
|
@ -236,14 +236,16 @@ if __name__ == '__main__':
|
|||
wallet.seed = None
|
||||
wallet.init_sequence(str(seed))
|
||||
else:
|
||||
wallet.seed = str(seed)
|
||||
wallet.init_mpk( wallet.seed )
|
||||
wallet.init_seed( str(seed) )
|
||||
|
||||
if not options.offline:
|
||||
interface = Interface(config)
|
||||
interface.start(wait=True)
|
||||
if not interface.start(wait=True):
|
||||
print_msg("Not connected, aborting.")
|
||||
sys.exit(1)
|
||||
wallet.interface = interface
|
||||
verifier = WalletVerifier(interface, config)
|
||||
verifier.start()
|
||||
wallet.set_verifier(verifier)
|
||||
|
||||
print_msg("Recovering wallet...")
|
||||
|
@ -345,7 +347,7 @@ if __name__ == '__main__':
|
|||
sys.exit(1)
|
||||
|
||||
if max_args < 0:
|
||||
if len(args) > min_args:
|
||||
if len(args) > min_args + 1:
|
||||
message = ' '.join(args[min_args:])
|
||||
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
|
||||
args = args[0:min_args] + [ message ]
|
||||
|
@ -358,9 +360,12 @@ if __name__ == '__main__':
|
|||
if cmd not in offline_commands and not options.offline:
|
||||
interface = Interface(config)
|
||||
interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
|
||||
interface.start()
|
||||
if not interface.start(wait=True):
|
||||
print_msg("Not connected, aborting.")
|
||||
sys.exit(1)
|
||||
wallet.interface = interface
|
||||
verifier = WalletVerifier(interface, config)
|
||||
verifier.start()
|
||||
wallet.set_verifier(verifier)
|
||||
synchronizer = WalletSynchronizer(wallet, config)
|
||||
synchronizer.start()
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
# do not remove this file
|
||||
from plugins import BasePlugin
|
||||
|
|
|
@ -36,7 +36,10 @@ class Exchanger(threading.Thread):
|
|||
response = connection.getresponse()
|
||||
if response.reason == httplib.responses[httplib.NOT_FOUND]:
|
||||
return
|
||||
response = json.loads(response.read())
|
||||
try:
|
||||
response = json.loads(response.read())
|
||||
except:
|
||||
return
|
||||
quote_currencies = {}
|
||||
try:
|
||||
for r in response:
|
||||
|
|
|
@ -61,8 +61,6 @@ elif platform.system() == 'Darwin':
|
|||
else:
|
||||
MONOSPACE_FONT = 'monospace'
|
||||
|
||||
ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
|
||||
|
||||
from electrum import ELECTRUM_VERSION
|
||||
import re
|
||||
|
||||
|
@ -226,7 +224,6 @@ class StatusBarButton(QPushButton):
|
|||
|
||||
|
||||
|
||||
|
||||
def waiting_dialog(f):
|
||||
|
||||
s = Timer()
|
||||
|
@ -248,19 +245,35 @@ def waiting_dialog(f):
|
|||
w.destroy()
|
||||
|
||||
|
||||
def ok_cancel_buttons(dialog):
|
||||
def ok_cancel_buttons(dialog, ok_label=_("OK") ):
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addStretch(1)
|
||||
b = QPushButton("Cancel")
|
||||
b = QPushButton(_("Cancel"))
|
||||
hbox.addWidget(b)
|
||||
b.clicked.connect(dialog.reject)
|
||||
b = QPushButton("OK")
|
||||
b = QPushButton(ok_label)
|
||||
hbox.addWidget(b)
|
||||
b.clicked.connect(dialog.accept)
|
||||
b.setDefault(True)
|
||||
return hbox
|
||||
|
||||
|
||||
def text_dialog(parent, title, label, ok_label):
|
||||
dialog = QDialog(parent)
|
||||
dialog.setMinimumWidth(500)
|
||||
dialog.setWindowTitle(title)
|
||||
dialog.setModal(1)
|
||||
l = QVBoxLayout()
|
||||
dialog.setLayout(l)
|
||||
l.addWidget(QLabel(label))
|
||||
txt = QTextEdit()
|
||||
l.addWidget(txt)
|
||||
l.addLayout(ok_cancel_buttons(dialog, ok_label))
|
||||
if dialog.exec_():
|
||||
return unicode(txt.toPlainText())
|
||||
|
||||
|
||||
|
||||
default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
|
||||
"receive":[[370],[370,200,130]] }
|
||||
|
||||
|
@ -327,44 +340,42 @@ class ElectrumWindow(QMainWindow):
|
|||
self.console.showMessage(self.wallet.banner)
|
||||
|
||||
# plugins that need to change the GUI do it here
|
||||
self.run_hook('init')
|
||||
self.run_hook('init_gui')
|
||||
|
||||
|
||||
# plugins
|
||||
def init_plugins(self):
|
||||
import imp, pkgutil
|
||||
if os.path.exists("plugins"):
|
||||
import imp, pkgutil, __builtin__
|
||||
if __builtin__.use_local_modules:
|
||||
fp, pathname, description = imp.find_module('plugins')
|
||||
plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
|
||||
plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
|
||||
imp.load_module('electrum_plugins', fp, pathname, description)
|
||||
plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
|
||||
self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
|
||||
plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
|
||||
else:
|
||||
import electrum_plugins
|
||||
plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
|
||||
self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
|
||||
plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
|
||||
|
||||
self.plugin_hooks = {}
|
||||
for p in self.plugins:
|
||||
self.plugins = []
|
||||
for p in plugins:
|
||||
try:
|
||||
p.init(self)
|
||||
self.plugins.append( p.Plugin(self) )
|
||||
except:
|
||||
print_msg("Error:cannot initialize plugin",p)
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
|
||||
def set_hook(self, name, callback):
|
||||
h = self.plugin_hooks.get(name, [])
|
||||
h.append(callback)
|
||||
self.plugin_hooks[name] = h
|
||||
|
||||
def unset_hook(self, name, callback):
|
||||
h = self.plugin_hooks.get(name,[])
|
||||
if callback in h: h.remove(callback)
|
||||
self.plugin_hooks[name] = h
|
||||
|
||||
def run_hook(self, name, args = ()):
|
||||
args = (self,) + args
|
||||
for cb in self.plugin_hooks.get(name,[]):
|
||||
apply(cb, args)
|
||||
def run_hook(self, name, *args):
|
||||
for p in self.plugins:
|
||||
if not p.is_enabled():
|
||||
continue
|
||||
try:
|
||||
f = eval('p.'+name)
|
||||
except:
|
||||
continue
|
||||
apply(f, args)
|
||||
return
|
||||
|
||||
|
||||
def set_label(self, name, text = None):
|
||||
|
@ -378,8 +389,7 @@ class ElectrumWindow(QMainWindow):
|
|||
if old_text:
|
||||
self.wallet.labels.pop(name)
|
||||
changed = True
|
||||
self.run_hook('set_label', (name, text, changed))
|
||||
|
||||
self.run_hook('set_label', name, text, changed)
|
||||
return changed
|
||||
|
||||
|
||||
|
@ -412,23 +422,6 @@ class ElectrumWindow(QMainWindow):
|
|||
def timer_actions(self):
|
||||
self.run_hook('timer_actions')
|
||||
|
||||
if self.payto_e.hasFocus():
|
||||
return
|
||||
r = unicode( self.payto_e.text() )
|
||||
if r != self.previous_payto_e:
|
||||
self.previous_payto_e = r
|
||||
r = r.strip()
|
||||
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
|
||||
try:
|
||||
to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
|
||||
except:
|
||||
return
|
||||
if to_address:
|
||||
s = r + ' <' + to_address + '>'
|
||||
self.payto_e.setText(s)
|
||||
|
||||
|
||||
|
||||
def update_status(self):
|
||||
if self.wallet.interface and self.wallet.interface.is_connected:
|
||||
if not self.wallet.up_to_date:
|
||||
|
@ -520,7 +513,7 @@ class ElectrumWindow(QMainWindow):
|
|||
vbox.addWidget(QLabel("Date: %s"%time_str))
|
||||
vbox.addWidget(QLabel("Status: %d confirmations"%conf))
|
||||
if is_mine:
|
||||
if fee:
|
||||
if fee is not None:
|
||||
vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
|
||||
vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
|
||||
else:
|
||||
|
@ -577,10 +570,11 @@ class ElectrumWindow(QMainWindow):
|
|||
|
||||
def address_label_clicked(self, item, column, l, column_addr, column_label):
|
||||
if column == column_label and item.isSelected():
|
||||
is_editable = item.data(0, 32).toBool()
|
||||
if not is_editable:
|
||||
return
|
||||
addr = unicode( item.text(column_addr) )
|
||||
label = unicode( item.text(column_label) )
|
||||
if label in self.wallet.aliases.keys():
|
||||
return
|
||||
item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
|
||||
l.editItem( item, column )
|
||||
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
|
||||
|
@ -590,26 +584,22 @@ class ElectrumWindow(QMainWindow):
|
|||
if column == column_label:
|
||||
addr = unicode( item.text(column_addr) )
|
||||
text = unicode( item.text(column_label) )
|
||||
changed = False
|
||||
is_editable = item.data(0, 32).toBool()
|
||||
if not is_editable:
|
||||
return
|
||||
|
||||
if text in self.wallet.aliases.keys():
|
||||
print_error("Error: This is one of your aliases")
|
||||
label = self.wallet.labels.get(addr,'')
|
||||
item.setText(column_label, QString(label))
|
||||
|
||||
else:
|
||||
changed = self.set_label(addr, text)
|
||||
if changed:
|
||||
self.update_history_tab()
|
||||
self.update_completions()
|
||||
changed = self.set_label(addr, text)
|
||||
if changed:
|
||||
self.update_history_tab()
|
||||
self.update_completions()
|
||||
|
||||
self.current_item_changed(item)
|
||||
|
||||
self.run_hook('item_changed', (item, column))
|
||||
self.run_hook('item_changed', item, column)
|
||||
|
||||
|
||||
def current_item_changed(self, a):
|
||||
self.run_hook('current_item_changed', (a,))
|
||||
self.run_hook('current_item_changed', a)
|
||||
|
||||
|
||||
|
||||
|
@ -755,7 +745,7 @@ class ElectrumWindow(QMainWindow):
|
|||
self.amount_e.textChanged.connect(lambda: entry_changed(False) )
|
||||
self.fee_e.textChanged.connect(lambda: entry_changed(True) )
|
||||
|
||||
self.run_hook('create_send_tab', (grid,))
|
||||
self.run_hook('create_send_tab', grid)
|
||||
return w2
|
||||
|
||||
|
||||
|
@ -764,8 +754,8 @@ class ElectrumWindow(QMainWindow):
|
|||
for addr,label in self.wallet.labels.items():
|
||||
if addr in self.wallet.addressbook:
|
||||
l.append( label + ' <' + addr + '>')
|
||||
l = l + self.wallet.aliases.keys()
|
||||
|
||||
self.run_hook('update_completions', l)
|
||||
self.completions.setStringList(l)
|
||||
|
||||
|
||||
|
@ -780,19 +770,9 @@ class ElectrumWindow(QMainWindow):
|
|||
r = unicode( self.payto_e.text() )
|
||||
r = r.strip()
|
||||
|
||||
# alias
|
||||
m1 = re.match(ALIAS_REGEXP, r)
|
||||
# label or alias, with address in brackets
|
||||
m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
|
||||
|
||||
if m1:
|
||||
to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
|
||||
if not to_address:
|
||||
return
|
||||
elif m2:
|
||||
to_address = m2.group(2)
|
||||
else:
|
||||
to_address = r
|
||||
m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
|
||||
to_address = m.group(2) if m else r
|
||||
|
||||
if not is_valid(to_address):
|
||||
QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
|
||||
|
@ -815,7 +795,7 @@ class ElectrumWindow(QMainWindow):
|
|||
self.show_message(str(e))
|
||||
return
|
||||
|
||||
self.run_hook('send_tx', (tx,))
|
||||
self.run_hook('send_tx', tx)
|
||||
|
||||
if label:
|
||||
self.set_label(tx.hash(), label)
|
||||
|
@ -844,10 +824,19 @@ class ElectrumWindow(QMainWindow):
|
|||
|
||||
|
||||
def set_url(self, url):
|
||||
payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
|
||||
address, amount, label, message, signature, identity, url = util.parse_url(url)
|
||||
|
||||
if label and self.wallet.labels.get(address) != label:
|
||||
if self.question('Give label "%s" to address %s ?'%(label,address)):
|
||||
if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
|
||||
self.wallet.addressbook.append(address)
|
||||
self.set_label(address, label)
|
||||
|
||||
self.run_hook('set_url', url, self.show_message, self.question)
|
||||
|
||||
self.tabs.setCurrentIndex(1)
|
||||
label = self.wallet.labels.get(payto)
|
||||
m_addr = label + ' <'+ payto+'>' if label else payto
|
||||
label = self.wallet.labels.get(address)
|
||||
m_addr = label + ' <'+ address +'>' if label else address
|
||||
self.payto_e.setText(m_addr)
|
||||
|
||||
self.message_e.setText(message)
|
||||
|
@ -1011,54 +1000,45 @@ class ElectrumWindow(QMainWindow):
|
|||
t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
|
||||
menu.addAction(t, lambda: self.toggle_priority(addr))
|
||||
|
||||
self.run_hook('receive_menu', (menu,))
|
||||
self.run_hook('receive_menu', menu)
|
||||
menu.exec_(self.receive_list.viewport().mapToGlobal(position))
|
||||
|
||||
|
||||
def payto(self, x, is_alias):
|
||||
if not x: return
|
||||
if is_alias:
|
||||
label = x
|
||||
m_addr = label
|
||||
else:
|
||||
addr = x
|
||||
label = self.wallet.labels.get(addr)
|
||||
m_addr = label + ' <' + addr + '>' if label else addr
|
||||
def payto(self, addr):
|
||||
if not addr: return
|
||||
label = self.wallet.labels.get(addr)
|
||||
m_addr = label + ' <' + addr + '>' if label else addr
|
||||
self.tabs.setCurrentIndex(1)
|
||||
self.payto_e.setText(m_addr)
|
||||
self.amount_e.setFocus()
|
||||
|
||||
def delete_contact(self, x, is_alias):
|
||||
|
||||
def delete_contact(self, x):
|
||||
if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
|
||||
if not is_alias and x in self.wallet.addressbook:
|
||||
if x in self.wallet.addressbook:
|
||||
self.wallet.addressbook.remove(x)
|
||||
self.set_label(x, None)
|
||||
elif is_alias and x in self.wallet.aliases:
|
||||
self.wallet.aliases.pop(x)
|
||||
self.update_history_tab()
|
||||
self.update_contacts_tab()
|
||||
self.update_completions()
|
||||
self.update_history_tab()
|
||||
self.update_contacts_tab()
|
||||
self.update_completions()
|
||||
|
||||
|
||||
def create_contact_menu(self, position):
|
||||
# fixme: this function apparently has a side effect.
|
||||
# if it is not called the menu pops up several times
|
||||
#self.contacts_list.selectedIndexes()
|
||||
|
||||
item = self.contacts_list.itemAt(position)
|
||||
if not item: return
|
||||
addr = unicode(item.text(0))
|
||||
label = unicode(item.text(1))
|
||||
is_alias = label in self.wallet.aliases.keys()
|
||||
x = label if is_alias else addr
|
||||
is_editable = item.data(0,32).toBool()
|
||||
payto_addr = item.data(0,33).toString()
|
||||
menu = QMenu()
|
||||
menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
|
||||
menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
|
||||
menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
|
||||
menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
|
||||
if not is_alias:
|
||||
if is_editable:
|
||||
menu.addAction(_("Edit label"), lambda: self.edit_label(False))
|
||||
else:
|
||||
menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
|
||||
menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
|
||||
menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
|
||||
|
||||
self.run_hook('create_contact_menu', menu, item)
|
||||
menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
|
||||
|
||||
|
||||
|
@ -1068,7 +1048,7 @@ class ElectrumWindow(QMainWindow):
|
|||
label = self.wallet.labels.get(address,'')
|
||||
item.setData(1,0,label)
|
||||
|
||||
self.run_hook('update_receive_item', (address, item))
|
||||
self.run_hook('update_receive_item', address, item)
|
||||
|
||||
c, u = self.wallet.get_addr_balance(address)
|
||||
balance = format_satoshis( c + u, False, self.wallet.num_zeros )
|
||||
|
@ -1114,13 +1094,12 @@ class ElectrumWindow(QMainWindow):
|
|||
for address in account[is_change]:
|
||||
h = self.wallet.history.get(address,[])
|
||||
|
||||
if not is_change:
|
||||
if h == []:
|
||||
gap += 1
|
||||
if gap > self.wallet.gap_limit:
|
||||
is_red = True
|
||||
else:
|
||||
gap = 0
|
||||
if h == []:
|
||||
gap += 1
|
||||
if gap > self.wallet.gap_limit:
|
||||
is_red = True
|
||||
else:
|
||||
gap = 0
|
||||
|
||||
num_tx = '*' if h == ['*'] else "%d"%len(h)
|
||||
item = QTreeWidgetItem( [ address, '', '', num_tx] )
|
||||
|
@ -1143,43 +1122,28 @@ class ElectrumWindow(QMainWindow):
|
|||
# we use column 1 because column 0 may be hidden
|
||||
l.setCurrentItem(l.topLevelItem(0),1)
|
||||
|
||||
def show_contact_details(self, m):
|
||||
a = self.wallet.aliases.get(m)
|
||||
if a:
|
||||
if a[0] in self.wallet.authorities.keys():
|
||||
s = self.wallet.authorities.get(a[0])
|
||||
else:
|
||||
s = "self-signed"
|
||||
msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
|
||||
QMessageBox.information(self, 'Alias', msg, 'OK')
|
||||
|
||||
def update_contacts_tab(self):
|
||||
|
||||
l = self.contacts_list
|
||||
l.clear()
|
||||
|
||||
alias_targets = []
|
||||
for alias, v in self.wallet.aliases.items():
|
||||
s, target = v
|
||||
alias_targets.append(target)
|
||||
item = QTreeWidgetItem( [ target, alias, '-'] )
|
||||
item.setBackgroundColor(0, QColor('lightgray'))
|
||||
l.addTopLevelItem(item)
|
||||
|
||||
for address in self.wallet.addressbook:
|
||||
if address in alias_targets: continue
|
||||
label = self.wallet.labels.get(address,'')
|
||||
n = 0
|
||||
for tx in self.wallet.transactions.values():
|
||||
if address in map(lambda x: x[0], tx.outputs): n += 1
|
||||
|
||||
n = self.wallet.get_num_tx(address)
|
||||
item = QTreeWidgetItem( [ address, label, "%d"%n] )
|
||||
item.setFont(0, QFont(MONOSPACE_FONT))
|
||||
# 32 = label can be edited (bool)
|
||||
item.setData(0,32, True)
|
||||
# 33 = payto string
|
||||
item.setData(0,33, address)
|
||||
l.addTopLevelItem(item)
|
||||
|
||||
self.run_hook('update_contacts_tab', l)
|
||||
l.setCurrentItem(l.topLevelItem(0))
|
||||
|
||||
|
||||
|
||||
def create_console_tab(self):
|
||||
from qt_console import Console
|
||||
self.console = console = Console()
|
||||
|
@ -1288,7 +1252,7 @@ class ElectrumWindow(QMainWindow):
|
|||
try:
|
||||
seed = self.wallet.decode_seed(password)
|
||||
except:
|
||||
QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
|
||||
QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
|
||||
return
|
||||
self.show_seed(seed, self)
|
||||
|
||||
|
@ -1520,6 +1484,7 @@ class ElectrumWindow(QMainWindow):
|
|||
vbox.addLayout(ok_cancel_buttons(d))
|
||||
d.setLayout(vbox)
|
||||
|
||||
self.run_hook('password_dialog', pw, grid, 1)
|
||||
if not d.exec_(): return
|
||||
return unicode(pw.text())
|
||||
|
||||
|
@ -1595,20 +1560,22 @@ class ElectrumWindow(QMainWindow):
|
|||
d.setModal(1)
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
|
||||
msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
|
||||
vbox.addWidget(QLabel(msg))
|
||||
|
||||
grid = QGridLayout()
|
||||
grid.setSpacing(8)
|
||||
|
||||
seed_e = QLineEdit()
|
||||
grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
|
||||
grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
|
||||
grid.addWidget(seed_e, 1, 1)
|
||||
grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
|
||||
|
||||
gap_e = QLineEdit()
|
||||
gap_e.setText("5")
|
||||
grid.addWidget(QLabel(_('Gap limit')), 2, 0)
|
||||
grid.addWidget(gap_e, 2, 1)
|
||||
grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
|
||||
gap_e.textChanged.connect(lambda: numbify(gap_e,True))
|
||||
vbox.addLayout(grid)
|
||||
|
||||
|
@ -1733,23 +1700,10 @@ class ElectrumWindow(QMainWindow):
|
|||
self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
|
||||
|
||||
def do_process_from_text(self):
|
||||
dialog = QDialog(self)
|
||||
dialog.setMinimumWidth(500)
|
||||
dialog.setWindowTitle(_('Input raw transaction'))
|
||||
dialog.setModal(1)
|
||||
l = QVBoxLayout()
|
||||
dialog.setLayout(l)
|
||||
l.addWidget(QLabel(_("Transaction:")))
|
||||
txt = QTextEdit()
|
||||
l.addWidget(txt)
|
||||
|
||||
ok_button = QPushButton(_("Load transaction"))
|
||||
ok_button.setDefault(True)
|
||||
ok_button.clicked.connect(dialog.accept)
|
||||
l.addWidget(ok_button)
|
||||
|
||||
dialog.exec_()
|
||||
tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
|
||||
text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
|
||||
if not text:
|
||||
return
|
||||
tx_dict = self.tx_dict_from_text(text)
|
||||
if tx_dict:
|
||||
self.create_process_transaction_window(tx_dict)
|
||||
|
||||
|
@ -1832,7 +1786,7 @@ class ElectrumWindow(QMainWindow):
|
|||
for key, value in json.loads(data).items():
|
||||
self.wallet.labels[key] = value
|
||||
self.wallet.save()
|
||||
QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
|
||||
QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
|
||||
except (IOError, os.error), reason:
|
||||
QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
|
||||
|
||||
|
@ -1857,24 +1811,34 @@ class ElectrumWindow(QMainWindow):
|
|||
@protected
|
||||
def do_import_privkey(self, password):
|
||||
if not self.wallet.imported_keys:
|
||||
r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
|
||||
+ _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
|
||||
r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
|
||||
+ _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
|
||||
+ _('In addition, when you send bitcoins from one of your imported addresses, the "change" will be sent to an address derived from your seed, unless you disabled this option.') + '<p>' \
|
||||
+ _('Are you sure you understand what you are doing?'), 3, 4)
|
||||
if r == 4: return
|
||||
|
||||
text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
|
||||
if not ok: return
|
||||
sec = str(text).strip()
|
||||
try:
|
||||
addr = self.wallet.import_key(sec, password)
|
||||
text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
|
||||
if not text: return
|
||||
|
||||
text = str(text).split()
|
||||
badkeys = []
|
||||
addrlist = []
|
||||
for key in text:
|
||||
try:
|
||||
addr = self.wallet.import_key(key, password)
|
||||
except BaseException as e:
|
||||
badkeys.append(key)
|
||||
continue
|
||||
if not addr:
|
||||
QMessageBox.critical(None, _("Unable to import key"), "error")
|
||||
badkeys.append(key)
|
||||
else:
|
||||
QMessageBox.information(None, _("Key imported"), addr)
|
||||
self.update_receive_tab()
|
||||
self.update_history_tab()
|
||||
except BaseException as e:
|
||||
QMessageBox.critical(None, _("Unable to import key"), str(e))
|
||||
addrlist.append(addr)
|
||||
if addrlist:
|
||||
QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
|
||||
if badkeys:
|
||||
QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
|
||||
self.update_receive_tab()
|
||||
self.update_history_tab()
|
||||
|
||||
|
||||
def settings_dialog(self):
|
||||
|
@ -2036,7 +2000,7 @@ class ElectrumWindow(QMainWindow):
|
|||
grid_plugins.setColumnStretch(0,1)
|
||||
tabs.addTab(tab5, _('Plugins') )
|
||||
def mk_toggle(cb, p):
|
||||
return lambda: cb.setChecked(p.toggle(self))
|
||||
return lambda: cb.setChecked(p.toggle())
|
||||
for i, p in enumerate(self.plugins):
|
||||
try:
|
||||
name, description = p.get_info()
|
||||
|
@ -2222,7 +2186,7 @@ class ElectrumWindow(QMainWindow):
|
|||
for p in protocol_letters:
|
||||
i = protocol_letters.index(p)
|
||||
j = server_protocol.model().index(i,0)
|
||||
if p not in pp.keys():
|
||||
if p not in pp.keys() and interface.is_connected:
|
||||
server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
|
||||
else:
|
||||
server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
|
||||
|
|
|
@ -868,14 +868,14 @@ class ElectrumWindow:
|
|||
self.show_message(tx_details)
|
||||
elif treeview == self.contacts_treeview:
|
||||
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
|
||||
a = self.wallet.aliases.get(m)
|
||||
if a:
|
||||
if a[0] in self.wallet.authorities.keys():
|
||||
s = self.wallet.authorities.get(a[0])
|
||||
else:
|
||||
s = "self-signed"
|
||||
msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
|
||||
self.show_message(msg)
|
||||
#a = self.wallet.aliases.get(m)
|
||||
#if a:
|
||||
# if a[0] in self.wallet.authorities.keys():
|
||||
# s = self.wallet.authorities.get(a[0])
|
||||
# else:
|
||||
# s = "self-signed"
|
||||
# msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
|
||||
# self.show_message(msg)
|
||||
|
||||
|
||||
def treeview_key_press(self, treeview, event):
|
||||
|
@ -890,14 +890,14 @@ class ElectrumWindow:
|
|||
self.show_message(tx_details)
|
||||
elif treeview == self.contacts_treeview:
|
||||
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
|
||||
a = self.wallet.aliases.get(m)
|
||||
if a:
|
||||
if a[0] in self.wallet.authorities.keys():
|
||||
s = self.wallet.authorities.get(a[0])
|
||||
else:
|
||||
s = "self"
|
||||
msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
|
||||
self.show_message(msg)
|
||||
#a = self.wallet.aliases.get(m)
|
||||
#if a:
|
||||
# if a[0] in self.wallet.authorities.keys():
|
||||
# s = self.wallet.authorities.get(a[0])
|
||||
# else:
|
||||
# s = "self"
|
||||
# msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
|
||||
# self.show_message(msg)
|
||||
|
||||
return False
|
||||
|
||||
|
@ -1145,17 +1145,14 @@ class ElectrumWindow:
|
|||
def update_sending_tab(self):
|
||||
# detect addresses that are not mine in history, add them here...
|
||||
self.addressbook_list.clear()
|
||||
for alias, v in self.wallet.aliases.items():
|
||||
s, target = v
|
||||
label = self.wallet.labels.get(alias)
|
||||
self.addressbook_list.append((alias, label, '-'))
|
||||
#for alias, v in self.wallet.aliases.items():
|
||||
# s, target = v
|
||||
# label = self.wallet.labels.get(alias)
|
||||
# self.addressbook_list.append((alias, label, '-'))
|
||||
|
||||
for address in self.wallet.addressbook:
|
||||
label = self.wallet.labels.get(address)
|
||||
n = 0
|
||||
for tx in self.wallet.transactions.values():
|
||||
if address in map(lambda x:x[0], tx.outputs): n += 1
|
||||
|
||||
n = self.wallet.get_num_tx(address)
|
||||
self.addressbook_list.append((address, label, "%d"%n))
|
||||
|
||||
def update_history_tab(self):
|
||||
|
@ -1176,7 +1173,7 @@ class ElectrumWindow:
|
|||
|
||||
label, is_default_label = self.wallet.get_label(tx_hash)
|
||||
tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
|
||||
details = self.wallet.get_tx_details(tx_hash)
|
||||
details = self.get_tx_details(tx_hash)
|
||||
|
||||
self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
|
||||
format_satoshis(value,True,self.wallet.num_zeros),
|
||||
|
@ -1184,6 +1181,40 @@ class ElectrumWindow:
|
|||
if cursor: self.history_treeview.set_cursor( cursor )
|
||||
|
||||
|
||||
def get_tx_details(self, tx_hash):
|
||||
import datetime
|
||||
if not tx_hash: return ''
|
||||
tx = self.wallet.transactions.get(tx_hash)
|
||||
is_mine, v, fee = self.wallet.get_tx_value(tx)
|
||||
conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
|
||||
|
||||
if timestamp:
|
||||
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
|
||||
else:
|
||||
time_str = 'pending'
|
||||
|
||||
inputs = map(lambda x: x.get('address'), tx.inputs)
|
||||
outputs = map(lambda x: x.get('address'), tx.d['outputs'])
|
||||
tx_details = "Transaction Details" +"\n\n" \
|
||||
+ "Transaction ID:\n" + tx_hash + "\n\n" \
|
||||
+ "Status: %d confirmations\n"%conf
|
||||
if is_mine:
|
||||
if fee:
|
||||
tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
|
||||
+ "Transaction fee: %s\n"% format_satoshis(fee, False)
|
||||
else:
|
||||
tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
|
||||
+ "Transaction fee: unknown\n"
|
||||
else:
|
||||
tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
|
||||
|
||||
tx_details += "Date: %s\n\n"%time_str \
|
||||
+ "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
|
||||
+ "Outputs:\n-"+ '\n-'.join(outputs)
|
||||
|
||||
return tx_details
|
||||
|
||||
|
||||
|
||||
def newaddress_dialog(self, w):
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
|
||||
class BasePlugin:
|
||||
|
||||
def get_info(self):
|
||||
return self.fullname, self.description
|
||||
|
||||
def __init__(self, gui, name, fullname, description):
|
||||
self.name = name
|
||||
self.fullname = fullname
|
||||
self.description = description
|
||||
self.gui = gui
|
||||
self.config = gui.config
|
||||
|
||||
def toggle(self):
|
||||
enabled = not self.is_enabled()
|
||||
self.set_enabled(enabled)
|
||||
self.init_gui()
|
||||
return enabled
|
||||
|
||||
def init_gui(self):
|
||||
pass
|
||||
|
||||
def is_enabled(self):
|
||||
return self.is_available() and self.config.get('use_'+self.name) is True
|
||||
|
||||
def is_available(self):
|
||||
return True
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
self.config.set_key('use_'+self.name, enabled, True)
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
|
@ -364,10 +364,9 @@ random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
|
|||
|
||||
def bip32_init(seed):
|
||||
import hmac
|
||||
|
||||
seed = seed.decode('hex')
|
||||
I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest()
|
||||
|
||||
print "seed", seed.encode('hex')
|
||||
master_secret = I[0:32]
|
||||
master_chain = I[32:]
|
||||
|
||||
|
@ -415,8 +414,8 @@ def CKD_prime(K, c, n):
|
|||
class ElectrumSequence:
|
||||
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
||||
|
||||
def __init__(self, master_public_key, mpk2 = None, mpk3 = None):
|
||||
self.master_public_key = master_public_key
|
||||
def __init__(self, mpk, mpk2 = None, mpk3 = None):
|
||||
self.mpk = mpk
|
||||
self.mpk2 = mpk2
|
||||
self.mpk3 = mpk3
|
||||
|
||||
|
@ -445,7 +444,7 @@ class ElectrumSequence:
|
|||
address = public_key_to_bc_address( pubkey.decode('hex') )
|
||||
elif not self.mpk3:
|
||||
pubkey1 = self.get_pubkey(sequence)
|
||||
pubkey2 = self.get_pubkey(sequence, use_mpk2=True)
|
||||
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
|
||||
address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
|
||||
else:
|
||||
pubkey1 = self.get_pubkey(sequence)
|
||||
|
@ -456,7 +455,7 @@ class ElectrumSequence:
|
|||
|
||||
def get_pubkey(self, sequence, mpk=None):
|
||||
curve = SECP256k1
|
||||
if mpk is None: mpk = self.master_public_key
|
||||
if mpk is None: mpk = self.mpk
|
||||
z = self.get_sequence(sequence, mpk)
|
||||
master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 )
|
||||
pubkey_point = master_public_key.pubkey.point + z*curve.generator
|
||||
|
@ -465,7 +464,7 @@ class ElectrumSequence:
|
|||
|
||||
def get_private_key_from_stretched_exponent(self, sequence, secexp):
|
||||
order = generator_secp256k1.order()
|
||||
secexp = ( secexp + self.get_sequence(sequence, self.master_public_key) ) % order
|
||||
secexp = ( secexp + self.get_sequence(sequence, self.mpk) ) % order
|
||||
pk = number_to_string( secexp, generator_secp256k1.order() )
|
||||
compressed = False
|
||||
return SecretToASecret( pk, compressed )
|
||||
|
@ -483,7 +482,7 @@ class ElectrumSequence:
|
|||
secexp = self.stretch_key(seed)
|
||||
master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
|
||||
master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
|
||||
if master_public_key != self.master_public_key:
|
||||
if master_public_key != self.mpk:
|
||||
print_error('invalid password (mpk)')
|
||||
raise BaseException('Invalid password')
|
||||
return True
|
||||
|
@ -499,8 +498,8 @@ class ElectrumSequence:
|
|||
redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
|
||||
else:
|
||||
pubkey1 = self.get_pubkey(sequence)
|
||||
pubkey2 = self.get_pubkey(sequence,mpk=self.mpk2)
|
||||
pubkey3 = self.get_pubkey(sequence,mpk=self.mpk3)
|
||||
pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
|
||||
pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3)
|
||||
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
|
||||
redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript']
|
||||
return pk_addr, redeemScript
|
||||
|
@ -510,43 +509,71 @@ class ElectrumSequence:
|
|||
|
||||
class BIP32Sequence:
|
||||
|
||||
def __init__(self, mpkc, mpkc2 = None):
|
||||
self.master_public_key, self.master_chain = mpkc
|
||||
if mpkc2:
|
||||
self.master_public_key2, self.master_chain2 = mpkc2
|
||||
self.is_p2sh = True
|
||||
else:
|
||||
self.is_p2sh = False
|
||||
def __init__(self, mpk, mpk2 = None, mpk3 = None):
|
||||
self.mpk = mpk
|
||||
self.mpk2 = mpk2
|
||||
self.mpk3 = mpk3
|
||||
|
||||
@classmethod
|
||||
def mpk_from_seed(klass, seed):
|
||||
master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
|
||||
return master_public_key.encode('hex'), master_chain.encode('hex')
|
||||
|
||||
def get_pubkey(self, sequence, use_mpk2=False):
|
||||
if not use_mpl2:
|
||||
K = self.master_public_key.decode('hex')
|
||||
chain = self.master_chain.decode('hex')
|
||||
else:
|
||||
K = self.master_public_key_2.decode('hex')
|
||||
chain = self.master_chain_2.decode('hex')
|
||||
def get_pubkey(self, sequence, mpk = None):
|
||||
if not mpk: mpk = self.mpk
|
||||
master_public_key, master_chain = self.mpk
|
||||
K = master_public_key.decode('hex')
|
||||
chain = master_chain.decode('hex')
|
||||
for i in sequence:
|
||||
K, K_compressed, chain = CKD_prime(K, chain, i)
|
||||
return K_compressed
|
||||
return K_compressed.encode('hex')
|
||||
|
||||
def get_address(self, sequence):
|
||||
return hash_160_to_bc_address(hash_160(self.get_pubkey(sequence)))
|
||||
if not self.mpk2:
|
||||
pubkey = self.get_pubkey(sequence)
|
||||
address = public_key_to_bc_address( pubkey.decode('hex') )
|
||||
elif not self.mpk3:
|
||||
pubkey1 = self.get_pubkey(sequence)
|
||||
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
|
||||
address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
|
||||
else:
|
||||
pubkey1 = self.get_pubkey(sequence)
|
||||
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
|
||||
pubkey3 = self.get_pubkey(sequence, mpk = self.mpk3)
|
||||
address = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)["address"]
|
||||
return address
|
||||
|
||||
def get_private_key(self, seed, sequence):
|
||||
k = self.master_secret
|
||||
chain = self.master_chain
|
||||
def get_private_key(self, sequence, seed):
|
||||
master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
|
||||
chain = master_chain
|
||||
k = master_secret
|
||||
for i in sequence:
|
||||
k, k_compressed, chain = CKD(k, chain, i)
|
||||
return SecretToASecret(k0, True)
|
||||
k, chain = CKD(k, chain, i)
|
||||
return SecretToASecret(k, True)
|
||||
|
||||
def get_private_keys(self, sequence_list, seed):
|
||||
return [ self.get_private_key( sequence, seed) for sequence in sequence_list]
|
||||
|
||||
def check_seed(self, seed):
|
||||
master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
|
||||
assert self.master_public_key == master_public_key
|
||||
assert self.mpk == (master_public_key.encode('hex'), master_chain.encode('hex'))
|
||||
|
||||
def get_input_info(self, sequence):
|
||||
if not self.mpk2:
|
||||
pk_addr = self.get_address(sequence)
|
||||
redeemScript = None
|
||||
elif not self.mpk3:
|
||||
pubkey1 = self.get_pubkey(sequence)
|
||||
pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
|
||||
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
|
||||
redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
|
||||
else:
|
||||
pubkey1 = self.get_pubkey(sequence)
|
||||
pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
|
||||
pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3)
|
||||
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
|
||||
redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript']
|
||||
return pk_addr, redeemScript
|
||||
|
||||
################################## transactions
|
||||
|
||||
|
@ -842,7 +869,7 @@ class Transaction:
|
|||
|
||||
|
||||
def test_bip32():
|
||||
seed = "ff000000000000000000000000000000".decode('hex')
|
||||
seed = "ff000000000000000000000000000000"
|
||||
master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
|
||||
|
||||
print "secret key", master_secret.encode('hex')
|
||||
|
|
|
@ -82,9 +82,10 @@ class Commands:
|
|||
self.wallet = wallet
|
||||
self.interface = interface
|
||||
self._callback = callback
|
||||
self.password = None
|
||||
|
||||
def _run(self, method, args, password_getter):
|
||||
if method in protected_commands:
|
||||
if method in protected_commands and self.wallet.use_encryption:
|
||||
self.password = apply(password_getter,())
|
||||
f = eval('self.'+method)
|
||||
result = apply(f,args)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
|
||||
from bitcoin import public_key_to_bc_address, hash_160_to_bc_address, hash_encode, hash_160
|
||||
from util import print_error
|
||||
#import socket
|
||||
import time
|
||||
import struct
|
||||
|
@ -356,7 +357,8 @@ def get_address_from_input_script(bytes):
|
|||
pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ]
|
||||
return pubkeys, signatures, hash_160_to_bc_address(hash_160(redeemScript), 5)
|
||||
|
||||
raise BaseException("no match for scriptsig")
|
||||
print_error("cannot find address in input script", bytes.encode('hex'))
|
||||
return [], [], "(None)"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -595,8 +595,8 @@ class Interface(threading.Thread):
|
|||
# wait until connection is established
|
||||
self.connect_event.wait()
|
||||
if not self.is_connected:
|
||||
print_msg("Not connected, aborting.")
|
||||
sys.exit(1)
|
||||
return False
|
||||
return True
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
|
|
35
lib/util.py
35
lib/util.py
|
@ -1,4 +1,4 @@
|
|||
import os, sys
|
||||
import os, sys, re
|
||||
import platform
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
@ -147,3 +147,36 @@ def age(from_date, since_date = None, target_tz=None, include_seconds=False):
|
|||
return "about 1 year ago"
|
||||
else:
|
||||
return "over %d years ago" % (round(distance_in_minutes / 525600))
|
||||
|
||||
|
||||
|
||||
|
||||
# URL decode
|
||||
_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
|
||||
urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
|
||||
|
||||
def parse_url(url):
|
||||
o = url[8:].split('?')
|
||||
address = o[0]
|
||||
if len(o)>1:
|
||||
params = o[1].split('&')
|
||||
else:
|
||||
params = []
|
||||
|
||||
amount = label = message = signature = identity = ''
|
||||
for p in params:
|
||||
k,v = p.split('=')
|
||||
uv = urldecode(v)
|
||||
if k == 'amount': amount = uv
|
||||
elif k == 'message': message = uv
|
||||
elif k == 'label': label = uv
|
||||
elif k == 'signature':
|
||||
identity, signature = uv.split(':')
|
||||
url = url.replace('&%s=%s'%(k,v),'')
|
||||
else:
|
||||
print k,v
|
||||
|
||||
return address, amount, label, message, signature, identity, url
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -49,14 +49,10 @@ class WalletVerifier(threading.Thread):
|
|||
def get_confirmations(self, tx):
|
||||
""" return the number of confirmations of a monitored transaction. """
|
||||
with self.lock:
|
||||
if tx in self.transactions.keys():
|
||||
if tx in self.verified_tx:
|
||||
height, timestamp = self.verified_tx[tx]
|
||||
conf = (self.local_height - height + 1)
|
||||
else:
|
||||
conf = -1
|
||||
if tx in self.verified_tx:
|
||||
height, timestamp = self.verified_tx[tx]
|
||||
conf = (self.local_height - height + 1)
|
||||
else:
|
||||
#print "verifier: tx not in list", tx
|
||||
conf = 0
|
||||
|
||||
if conf <= 0:
|
||||
|
@ -65,6 +61,13 @@ class WalletVerifier(threading.Thread):
|
|||
return conf, timestamp
|
||||
|
||||
|
||||
def get_height(self, tx_hash):
|
||||
with self.lock:
|
||||
v = self.verified_tx.get(tx_hash)
|
||||
height = v[0] if v else None
|
||||
return height
|
||||
|
||||
|
||||
def add(self, tx_hash, tx_height):
|
||||
""" add a transaction to the list of monitored transactions. """
|
||||
assert tx_height > 0
|
||||
|
@ -145,6 +148,10 @@ class WalletVerifier(threading.Thread):
|
|||
continue
|
||||
if not r: continue
|
||||
|
||||
if r.get('error'):
|
||||
print_error('Verifier received an error:', r)
|
||||
continue
|
||||
|
||||
# 3. handle response
|
||||
method = r['method']
|
||||
params = r['params']
|
||||
|
@ -183,7 +190,8 @@ class WalletVerifier(threading.Thread):
|
|||
# we passed all the tests
|
||||
header = self.read_header(tx_height)
|
||||
timestamp = header.get('timestamp')
|
||||
self.verified_tx[tx_hash] = (tx_height, timestamp)
|
||||
with self.lock:
|
||||
self.verified_tx[tx_hash] = (tx_height, timestamp)
|
||||
print_error("verified %s"%tx_hash)
|
||||
self.config.set_key('verified_tx2', self.verified_tx, True)
|
||||
self.interface.trigger_callback('updated')
|
||||
|
@ -241,12 +249,16 @@ class WalletVerifier(threading.Thread):
|
|||
# this can be caused by a reorg.
|
||||
print_error("verify header failed"+ repr(header))
|
||||
# undo verifications
|
||||
for tx_hash, item in self.verified_tx.items():
|
||||
with self.lock:
|
||||
items = self.verified_tx.items()[:]
|
||||
for tx_hash, item in items:
|
||||
tx_height, timestamp = item
|
||||
if tx_height >= height:
|
||||
print_error("redoing", tx_hash)
|
||||
self.verified_tx.pop(tx_hash)
|
||||
if tx_hash in self.merkle_roots: self.merkle_roots.pop(tx_hash)
|
||||
with self.lock:
|
||||
self.verified_tx.pop(tx_hash)
|
||||
if tx_hash in self.merkle_roots:
|
||||
self.merkle_roots.pop(tx_hash)
|
||||
# return False to request previous header.
|
||||
return False
|
||||
|
||||
|
|
271
lib/wallet.py
271
lib/wallet.py
|
@ -33,9 +33,6 @@ import time
|
|||
from util import print_msg, print_error, user_dir, format_satoshis
|
||||
from bitcoin import *
|
||||
|
||||
# URL decode
|
||||
_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
|
||||
urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
|
||||
|
||||
# AES encryption
|
||||
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
|
||||
|
@ -82,19 +79,16 @@ class Wallet:
|
|||
self.use_encryption = config.get('use_encryption', False)
|
||||
self.seed = config.get('seed', '') # encrypted
|
||||
self.labels = config.get('labels', {})
|
||||
self.aliases = config.get('aliases', {}) # aliases for addresses
|
||||
self.authorities = config.get('authorities', {}) # trusted addresses
|
||||
self.frozen_addresses = config.get('frozen_addresses',[])
|
||||
self.prioritized_addresses = config.get('prioritized_addresses',[])
|
||||
self.receipts = config.get('receipts',{}) # signed URIs
|
||||
self.addressbook = config.get('contacts', [])
|
||||
self.imported_keys = config.get('imported_keys',{})
|
||||
self.history = config.get('addr_history',{}) # address -> list(txid, height)
|
||||
self.tx_height = config.get('tx_height',{})
|
||||
self.accounts = config.get('accounts', {}) # this should not include public keys
|
||||
|
||||
self.SequenceClass = ElectrumSequence
|
||||
self.sequences = {}
|
||||
self.sequences[0] = ElectrumSequence(self.config.get('master_public_key'))
|
||||
self.sequences[0] = self.SequenceClass(self.config.get('master_public_key'))
|
||||
|
||||
if self.accounts.get(0) is None:
|
||||
self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
|
||||
|
@ -161,13 +155,13 @@ class Wallet:
|
|||
self.seed = seed
|
||||
self.config.set_key('seed', self.seed, True)
|
||||
self.config.set_key('seed_version', self.seed_version, True)
|
||||
mpk = ElectrumSequence.mpk_from_seed(self.seed)
|
||||
mpk = self.SequenceClass.mpk_from_seed(self.seed)
|
||||
self.init_sequence(mpk)
|
||||
|
||||
|
||||
def init_sequence(self, mpk):
|
||||
self.config.set_key('master_public_key', mpk, True)
|
||||
self.sequences[0] = ElectrumSequence(mpk)
|
||||
self.sequences[0] = self.SequenceClass(mpk)
|
||||
self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
|
||||
self.config.set_key('accounts', self.accounts, True)
|
||||
|
||||
|
@ -184,8 +178,8 @@ class Wallet:
|
|||
return address in self.addresses(True)
|
||||
|
||||
def is_change(self, address):
|
||||
#return address in self.change_addresses
|
||||
return False
|
||||
acct, s = self.get_address_index(address)
|
||||
return s[0] == 1
|
||||
|
||||
def get_master_public_key(self):
|
||||
return self.sequences[0].master_public_key
|
||||
|
@ -302,6 +296,7 @@ class Wallet:
|
|||
address = self.get_new_address( account, for_change, n)
|
||||
self.accounts[account][for_change].append(address)
|
||||
self.history[address] = []
|
||||
print_msg(address)
|
||||
return address
|
||||
|
||||
|
||||
|
@ -412,6 +407,12 @@ class Wallet:
|
|||
# redo labels
|
||||
# self.update_tx_labels()
|
||||
|
||||
def get_num_tx(self, address):
|
||||
n = 0
|
||||
for tx in self.transactions.values():
|
||||
if address in map(lambda x:x[0], tx.outputs): n += 1
|
||||
return n
|
||||
|
||||
|
||||
def get_address_flags(self, addr):
|
||||
flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-"
|
||||
|
@ -424,46 +425,6 @@ class Wallet:
|
|||
return tx.get_value(addresses, self.prevout_values)
|
||||
|
||||
|
||||
def get_tx_details(self, tx_hash):
|
||||
import datetime
|
||||
if not tx_hash: return ''
|
||||
tx = self.transactions.get(tx_hash)
|
||||
is_mine, v, fee = self.get_tx_value(tx)
|
||||
conf, timestamp = self.verifier.get_confirmations(tx_hash)
|
||||
|
||||
if timestamp:
|
||||
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
|
||||
else:
|
||||
time_str = 'pending'
|
||||
|
||||
inputs = map(lambda x: x.get('address'), tx.inputs)
|
||||
outputs = map(lambda x: x.get('address'), tx.d['outputs'])
|
||||
tx_details = "Transaction Details" +"\n\n" \
|
||||
+ "Transaction ID:\n" + tx_hash + "\n\n" \
|
||||
+ "Status: %d confirmations\n"%conf
|
||||
if is_mine:
|
||||
if fee:
|
||||
tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
|
||||
+ "Transaction fee: %s\n"% format_satoshis(fee, False)
|
||||
else:
|
||||
tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
|
||||
+ "Transaction fee: unknown\n"
|
||||
else:
|
||||
tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
|
||||
|
||||
tx_details += "Date: %s\n\n"%time_str \
|
||||
+ "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
|
||||
+ "Outputs:\n-"+ '\n-'.join(outputs)
|
||||
|
||||
r = self.receipts.get(tx_hash)
|
||||
if r:
|
||||
tx_details += "\n_______________________________________" \
|
||||
+ '\n\nSigned URI: ' + r[2] \
|
||||
+ "\n\nSigned by: " + r[0] \
|
||||
+ '\n\nSignature: ' + r[1]
|
||||
|
||||
return tx_details
|
||||
|
||||
|
||||
def update_tx_outputs(self, tx_hash):
|
||||
tx = self.transactions.get(tx_hash)
|
||||
|
@ -566,6 +527,7 @@ class Wallet:
|
|||
if h == ['*']: continue
|
||||
for tx_hash, tx_height in h:
|
||||
tx = self.transactions.get(tx_hash)
|
||||
if tx is None: raise BaseException("Wallet not synchronized")
|
||||
for output in tx.d.get('outputs'):
|
||||
if output.get('address') != addr: continue
|
||||
key = tx_hash + ":%d" % output.get('index')
|
||||
|
@ -640,11 +602,12 @@ class Wallet:
|
|||
def receive_tx_callback(self, tx_hash, tx, tx_height):
|
||||
|
||||
if not self.check_new_tx(tx_hash, tx):
|
||||
raise BaseException("error: received transaction is not consistent with history", tx_hash)
|
||||
# may happen due to pruning
|
||||
print_error("received transaction that is no longer referenced in history", tx_hash)
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
self.transactions[tx_hash] = tx
|
||||
self.tx_height[tx_hash] = tx_height
|
||||
|
||||
#tx_height = tx.get('height')
|
||||
if self.verifier and tx_height>0:
|
||||
|
@ -669,17 +632,12 @@ class Wallet:
|
|||
if tx_height>0:
|
||||
# add it in case it was previously unconfirmed
|
||||
if self.verifier: self.verifier.add(tx_hash, tx_height)
|
||||
# set the height in case it changed
|
||||
txh = self.tx_height.get(tx_hash)
|
||||
if txh is not None and txh != tx_height:
|
||||
print_error( "changing height for tx", tx_hash )
|
||||
self.tx_height[tx_hash] = tx_height
|
||||
|
||||
|
||||
def get_tx_history(self):
|
||||
with self.lock:
|
||||
history = self.transactions.items()
|
||||
history.sort(key = lambda x: self.tx_height.get(x[0]) if self.tx_height.get(x[0]) else 1e12)
|
||||
history.sort(key = lambda x: self.verifier.get_height(x[0]) if self.verifier.get_height(x[0]) else 1e12)
|
||||
result = []
|
||||
|
||||
balance = 0
|
||||
|
@ -724,6 +682,9 @@ class Wallet:
|
|||
default_label = self.labels[o_addr]
|
||||
except KeyError:
|
||||
default_label = o_addr
|
||||
break
|
||||
else:
|
||||
default_label = '(internal)'
|
||||
else:
|
||||
for o in tx.outputs:
|
||||
o_addr, _ = o
|
||||
|
@ -812,48 +773,6 @@ class Wallet:
|
|||
return True, out
|
||||
|
||||
|
||||
def read_alias(self, alias):
|
||||
# this might not be the right place for this function.
|
||||
import urllib
|
||||
|
||||
m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
|
||||
m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
|
||||
if m1:
|
||||
url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1)
|
||||
elif m2:
|
||||
url = 'https://' + alias + '/bitcoin.id'
|
||||
else:
|
||||
return ''
|
||||
try:
|
||||
lines = urllib.urlopen(url).readlines()
|
||||
except:
|
||||
return ''
|
||||
|
||||
# line 0
|
||||
line = lines[0].strip().split(':')
|
||||
if len(line) == 1:
|
||||
auth_name = None
|
||||
target = signing_addr = line[0]
|
||||
else:
|
||||
target, auth_name, signing_addr, signature = line
|
||||
msg = "alias:%s:%s:%s"%(alias,target,auth_name)
|
||||
print msg, signature
|
||||
EC_KEY.verify_message(signing_addr, signature, msg)
|
||||
|
||||
# other lines are signed updates
|
||||
for line in lines[1:]:
|
||||
line = line.strip()
|
||||
if not line: continue
|
||||
line = line.split(':')
|
||||
previous = target
|
||||
print repr(line)
|
||||
target, signature = line
|
||||
EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
|
||||
|
||||
if not is_valid(target):
|
||||
raise ValueError("Invalid bitcoin address")
|
||||
|
||||
return target, signing_addr, auth_name
|
||||
|
||||
def update_password(self, seed, old_password, new_password):
|
||||
if new_password == '': new_password = None
|
||||
|
@ -867,96 +786,6 @@ class Wallet:
|
|||
self.imported_keys[k] = c
|
||||
self.save()
|
||||
|
||||
def get_alias(self, alias, interactive = False, show_message=None, question = None):
|
||||
try:
|
||||
target, signing_address, auth_name = self.read_alias(alias)
|
||||
except BaseException, e:
|
||||
# raise exception if verify fails (verify the chain)
|
||||
if interactive:
|
||||
show_message("Alias error: " + str(e))
|
||||
return
|
||||
|
||||
print target, signing_address, auth_name
|
||||
|
||||
if auth_name is None:
|
||||
a = self.aliases.get(alias)
|
||||
if not a:
|
||||
msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
|
||||
if interactive and question( msg ):
|
||||
self.aliases[alias] = (signing_address, target)
|
||||
else:
|
||||
target = None
|
||||
else:
|
||||
if signing_address != a[0]:
|
||||
msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
|
||||
if interactive and question( msg ):
|
||||
self.aliases[alias] = (signing_address, target)
|
||||
else:
|
||||
target = None
|
||||
else:
|
||||
if signing_address not in self.authorities.keys():
|
||||
msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
|
||||
if interactive and question( msg ):
|
||||
self.authorities[signing_address] = auth_name
|
||||
else:
|
||||
target = None
|
||||
|
||||
if target:
|
||||
self.aliases[alias] = (signing_address, target)
|
||||
|
||||
return target
|
||||
|
||||
|
||||
def parse_url(self, url, show_message, question):
|
||||
o = url[8:].split('?')
|
||||
address = o[0]
|
||||
if len(o)>1:
|
||||
params = o[1].split('&')
|
||||
else:
|
||||
params = []
|
||||
|
||||
amount = label = message = signature = identity = ''
|
||||
for p in params:
|
||||
k,v = p.split('=')
|
||||
uv = urldecode(v)
|
||||
if k == 'amount': amount = uv
|
||||
elif k == 'message': message = uv
|
||||
elif k == 'label': label = uv
|
||||
elif k == 'signature':
|
||||
identity, signature = uv.split(':')
|
||||
url = url.replace('&%s=%s'%(k,v),'')
|
||||
else:
|
||||
print k,v
|
||||
|
||||
if label and self.labels.get(address) != label:
|
||||
if question('Give label "%s" to address %s ?'%(label,address)):
|
||||
if address not in self.addressbook and not self.is_mine(address):
|
||||
self.addressbook.append(address)
|
||||
self.labels[address] = label
|
||||
|
||||
if signature:
|
||||
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
|
||||
signing_address = self.get_alias(identity, True, show_message, question)
|
||||
elif is_valid(identity):
|
||||
signing_address = identity
|
||||
else:
|
||||
signing_address = None
|
||||
if not signing_address:
|
||||
return
|
||||
try:
|
||||
EC_KEY.verify_message(signing_address, signature, url )
|
||||
self.receipt = (signing_address, signature, url)
|
||||
except:
|
||||
show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
|
||||
address = amount = label = identity = message = ''
|
||||
|
||||
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
|
||||
payto_address = self.get_alias(address, True, show_message, question)
|
||||
if payto_address:
|
||||
address = address + ' <' + payto_address + '>'
|
||||
|
||||
return address, amount, label, message, signature, identity, url
|
||||
|
||||
|
||||
|
||||
def freeze(self,addr):
|
||||
|
@ -1007,15 +836,11 @@ class Wallet:
|
|||
'labels': self.labels,
|
||||
'contacts': self.addressbook,
|
||||
'imported_keys': self.imported_keys,
|
||||
'aliases': self.aliases,
|
||||
'authorities': self.authorities,
|
||||
'receipts': self.receipts,
|
||||
'num_zeros': self.num_zeros,
|
||||
'frozen_addresses': self.frozen_addresses,
|
||||
'prioritized_addresses': self.prioritized_addresses,
|
||||
'gap_limit': self.gap_limit,
|
||||
'transactions': tx,
|
||||
'tx_height': self.tx_height,
|
||||
}
|
||||
for k, v in s.items():
|
||||
self.config.set_key(k,v)
|
||||
|
@ -1024,17 +849,6 @@ class Wallet:
|
|||
def set_verifier(self, verifier):
|
||||
self.verifier = verifier
|
||||
|
||||
# review stored transactions and send them to the verifier
|
||||
# (they are not necessarily in the history, because history items might have have been pruned)
|
||||
for tx_hash, tx in self.transactions.items():
|
||||
tx_height = self.tx_height[tx_hash]
|
||||
if tx_height <1:
|
||||
print_error( "skipping", tx_hash, tx_height )
|
||||
continue
|
||||
|
||||
if tx_height>0:
|
||||
self.verifier.add(tx_hash, tx_height)
|
||||
|
||||
# review transactions that are in the history
|
||||
for addr, hist in self.history.items():
|
||||
if hist == ['*']: continue
|
||||
|
@ -1042,11 +856,6 @@ class Wallet:
|
|||
if tx_height>0:
|
||||
# add it in case it was previously unconfirmed
|
||||
self.verifier.add(tx_hash, tx_height)
|
||||
# set the height in case it changed
|
||||
txh = self.tx_height.get(tx_hash)
|
||||
if txh is not None and txh != tx_height:
|
||||
print_error( "changing height for tx", tx_hash )
|
||||
self.tx_height[tx_hash] = tx_height
|
||||
|
||||
|
||||
|
||||
|
@ -1082,7 +891,7 @@ class Wallet:
|
|||
if not tx: continue
|
||||
|
||||
# already verified?
|
||||
if self.tx_height.get(tx_hash):
|
||||
if self.verifier.get_height(tx_hash):
|
||||
continue
|
||||
# unconfirmed tx
|
||||
print_error("new history is orphaning transaction:", tx_hash)
|
||||
|
@ -1099,7 +908,6 @@ class Wallet:
|
|||
for item in h:
|
||||
if item.get('tx_hash') == tx_hash:
|
||||
height = item.get('height')
|
||||
self.tx_height[tx_hash] = height
|
||||
if height:
|
||||
print_error("found height for", tx_hash, height)
|
||||
self.verifier.add(tx_hash, height)
|
||||
|
@ -1155,22 +963,6 @@ class WalletSynchronizer(threading.Thread):
|
|||
def is_running(self):
|
||||
with self.lock: return self.running
|
||||
|
||||
def synchronize_wallet(self):
|
||||
new_addresses = self.wallet.synchronize()
|
||||
if new_addresses:
|
||||
self.subscribe_to_addresses(new_addresses)
|
||||
self.wallet.up_to_date = False
|
||||
return
|
||||
|
||||
if not self.interface.is_up_to_date('synchronizer'):
|
||||
if self.wallet.is_up_to_date():
|
||||
self.wallet.set_up_to_date(False)
|
||||
self.was_updated = True
|
||||
return
|
||||
|
||||
self.wallet.set_up_to_date(True)
|
||||
self.was_updated = True
|
||||
|
||||
|
||||
def subscribe_to_addresses(self, addresses):
|
||||
messages = []
|
||||
|
@ -1202,15 +994,30 @@ class WalletSynchronizer(threading.Thread):
|
|||
self.subscribe_to_addresses(self.wallet.addresses(True))
|
||||
|
||||
while self.is_running():
|
||||
# 1. send new requests
|
||||
self.synchronize_wallet()
|
||||
# 1. create new addresses
|
||||
new_addresses = self.wallet.synchronize()
|
||||
|
||||
# request missing addresses
|
||||
if new_addresses:
|
||||
self.subscribe_to_addresses(new_addresses)
|
||||
|
||||
# request missing transactions
|
||||
for tx_hash, tx_height in missing_tx:
|
||||
if (tx_hash, tx_height) not in requested_tx:
|
||||
self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
|
||||
requested_tx.append( (tx_hash, tx_height) )
|
||||
missing_tx = []
|
||||
|
||||
# detect if situation has changed
|
||||
if not self.interface.is_up_to_date('synchronizer'):
|
||||
if self.wallet.is_up_to_date():
|
||||
self.wallet.set_up_to_date(False)
|
||||
self.was_updated = True
|
||||
else:
|
||||
if not self.wallet.is_up_to_date():
|
||||
self.wallet.set_up_to_date(True)
|
||||
self.was_updated = True
|
||||
|
||||
if self.was_updated:
|
||||
self.interface.trigger_callback('updated')
|
||||
self.was_updated = False
|
||||
|
|
|
@ -10,7 +10,7 @@ if __name__ == '__main__':
|
|||
sys.exit()
|
||||
|
||||
os.system("python mki18n.py")
|
||||
os.system("pyrcc4 icons.qrc -o lib/icons_rc.py")
|
||||
os.system("pyrcc4 icons.qrc -o gui/icons_rc.py")
|
||||
os.system("python setup.py sdist --format=zip,gztar")
|
||||
|
||||
_tgz="Electrum-%s.tar.gz"%version
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
import re
|
||||
import platform
|
||||
from decimal import Decimal
|
||||
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
import PyQt4.QtCore as QtCore
|
||||
import PyQt4.QtGui as QtGui
|
||||
|
||||
from electrum_gui.qrcodewidget import QRCodeWidget
|
||||
from electrum_gui import bmp, pyqrnative
|
||||
from electrum_gui.i18n import _
|
||||
|
||||
|
||||
ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
|
||||
|
||||
|
||||
|
||||
from electrum_gui import BasePlugin
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def __init__(self, gui):
|
||||
BasePlugin.__init__(self, gui, 'aliases', 'Aliases', _('Retrieve aliases using http.'))
|
||||
self.aliases = self.config.get('aliases', {}) # aliases for addresses
|
||||
self.authorities = self.config.get('authorities', {}) # trusted addresses
|
||||
self.receipts = self.config.get('receipts',{}) # signed URIs
|
||||
|
||||
|
||||
def timer_actions(self):
|
||||
if self.gui.payto_e.hasFocus():
|
||||
return
|
||||
r = unicode( self.gui.payto_e.text() )
|
||||
if r != self.gui.previous_payto_e:
|
||||
self.gui.previous_payto_e = r
|
||||
r = r.strip()
|
||||
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
|
||||
try:
|
||||
to_address = self.get_alias(r, True, self.gui.show_message, self.gui.question)
|
||||
except:
|
||||
return
|
||||
if to_address:
|
||||
s = r + ' <' + to_address + '>'
|
||||
self.gui.payto_e.setText(s)
|
||||
|
||||
|
||||
def get_alias(self, alias, interactive = False, show_message=None, question = None):
|
||||
try:
|
||||
target, signing_address, auth_name = read_alias(self, alias)
|
||||
except BaseException, e:
|
||||
# raise exception if verify fails (verify the chain)
|
||||
if interactive:
|
||||
show_message("Alias error: " + str(e))
|
||||
return
|
||||
|
||||
print target, signing_address, auth_name
|
||||
|
||||
if auth_name is None:
|
||||
a = self.aliases.get(alias)
|
||||
if not a:
|
||||
msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
|
||||
if interactive and question( msg ):
|
||||
self.aliases[alias] = (signing_address, target)
|
||||
else:
|
||||
target = None
|
||||
else:
|
||||
if signing_address != a[0]:
|
||||
msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
|
||||
if interactive and question( msg ):
|
||||
self.aliases[alias] = (signing_address, target)
|
||||
else:
|
||||
target = None
|
||||
else:
|
||||
if signing_address not in self.authorities.keys():
|
||||
msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
|
||||
if interactive and question( msg ):
|
||||
self.authorities[signing_address] = auth_name
|
||||
else:
|
||||
target = None
|
||||
|
||||
if target:
|
||||
self.aliases[alias] = (signing_address, target)
|
||||
|
||||
return target
|
||||
|
||||
|
||||
|
||||
def read_alias(self, alias):
|
||||
import urllib
|
||||
|
||||
m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
|
||||
m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
|
||||
if m1:
|
||||
url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1)
|
||||
elif m2:
|
||||
url = 'https://' + alias + '/bitcoin.id'
|
||||
else:
|
||||
return ''
|
||||
try:
|
||||
lines = urllib.urlopen(url).readlines()
|
||||
except:
|
||||
return ''
|
||||
|
||||
# line 0
|
||||
line = lines[0].strip().split(':')
|
||||
if len(line) == 1:
|
||||
auth_name = None
|
||||
target = signing_addr = line[0]
|
||||
else:
|
||||
target, auth_name, signing_addr, signature = line
|
||||
msg = "alias:%s:%s:%s"%(alias,target,auth_name)
|
||||
print msg, signature
|
||||
EC_KEY.verify_message(signing_addr, signature, msg)
|
||||
|
||||
# other lines are signed updates
|
||||
for line in lines[1:]:
|
||||
line = line.strip()
|
||||
if not line: continue
|
||||
line = line.split(':')
|
||||
previous = target
|
||||
print repr(line)
|
||||
target, signature = line
|
||||
EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
|
||||
|
||||
if not is_valid(target):
|
||||
raise ValueError("Invalid bitcoin address")
|
||||
|
||||
return target, signing_addr, auth_name
|
||||
|
||||
|
||||
def set_url(self, url, show_message, question):
|
||||
payto, amount, label, message, signature, identity, url = util.parse_url(url)
|
||||
if signature:
|
||||
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
|
||||
signing_address = get_alias(identity, True, show_message, question)
|
||||
elif is_valid(identity):
|
||||
signing_address = identity
|
||||
else:
|
||||
signing_address = None
|
||||
if not signing_address:
|
||||
return
|
||||
try:
|
||||
EC_KEY.verify_message(signing_address, signature, url )
|
||||
self.receipt = (signing_address, signature, url)
|
||||
except:
|
||||
show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
|
||||
address = amount = label = identity = message = ''
|
||||
|
||||
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
|
||||
payto_address = get_alias(address, True, show_message, question)
|
||||
if payto_address:
|
||||
address = address + ' <' + payto_address + '>'
|
||||
|
||||
return address, amount, label, message, signature, identity, url
|
||||
|
||||
|
||||
|
||||
def update_contacts_tab(self, l):
|
||||
alias_targets = []
|
||||
for alias, v in self.aliases.items():
|
||||
s, target = v
|
||||
alias_targets.append(target)
|
||||
item = QTreeWidgetItem( [ target, alias, '-'] )
|
||||
item.setBackgroundColor(0, QColor('lightgray'))
|
||||
item.setData(0,32,False)
|
||||
item.setData(0,33,alias + ' <' + target + '>')
|
||||
l.insertTopLevelItem(0,item)
|
||||
|
||||
|
||||
def update_completions(self, l):
|
||||
l[:] = l + self.aliases.keys()
|
||||
|
||||
|
||||
def create_contact_menu(self, menu, item):
|
||||
label = unicode(item.text(1))
|
||||
if label in self.aliases.keys():
|
||||
addr = unicode(item.text(0))
|
||||
label = unicode(item.text(1))
|
||||
menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
|
||||
menu.addAction(_("Delete alias"), lambda: delete_alias(self, label))
|
||||
|
||||
|
||||
def show_contact_details(self, m):
|
||||
a = self.aliases.get(m)
|
||||
if a:
|
||||
if a[0] in self.authorities.keys():
|
||||
s = self.authorities.get(a[0])
|
||||
else:
|
||||
s = "self-signed"
|
||||
msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
|
||||
QMessageBox.information(self.gui, 'Alias', msg, 'OK')
|
||||
|
||||
|
||||
def delete_alias(self, x):
|
||||
if self.gui.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
|
||||
if x in self.aliases:
|
||||
self.aliases.pop(x)
|
||||
self.update_history_tab()
|
||||
self.update_contacts_tab()
|
||||
self.update_completions()
|
|
@ -8,8 +8,7 @@ import PyQt4.QtCore as QtCore
|
|||
import PyQt4.QtGui as QtGui
|
||||
|
||||
from electrum_gui.qrcodewidget import QRCodeWidget
|
||||
from electrum_gui import bmp, pyqrnative
|
||||
|
||||
from electrum_gui import bmp, pyqrnative, BasePlugin
|
||||
from electrum_gui.i18n import _
|
||||
|
||||
|
||||
|
@ -92,95 +91,92 @@ class QR_Window(QWidget):
|
|||
|
||||
|
||||
|
||||
config = {}
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def get_info():
|
||||
return 'Point of Sale', _('Show QR code window and amounts requested for each address. Add menu item to request amount.')
|
||||
|
||||
def init(gui):
|
||||
global config
|
||||
config = gui.config
|
||||
gui.requested_amounts = config.get('requested_amounts',{})
|
||||
gui.merchant_name = config.get('merchant_name', 'Invoice')
|
||||
gui.qr_window = None
|
||||
do_enable(gui, is_enabled())
|
||||
|
||||
def is_enabled():
|
||||
return config.get('pointofsale') is True
|
||||
|
||||
def is_available():
|
||||
return True
|
||||
def __init__(self, gui):
|
||||
BasePlugin.__init__(self, gui, 'pointofsale', 'Point of Sale',
|
||||
_('Show QR code window and amounts requested for each address. Add menu item to request amount.') )
|
||||
self.qr_window = None
|
||||
self.requested_amounts = self.config.get('requested_amounts',{})
|
||||
self.merchant_name = self.config.get('merchant_name', 'Invoice')
|
||||
|
||||
|
||||
def toggle(gui):
|
||||
enabled = not is_enabled()
|
||||
config.set_key('pointofsale', enabled, True)
|
||||
do_enable(gui, enabled)
|
||||
update_gui(gui)
|
||||
return enabled
|
||||
def init_gui(self):
|
||||
enabled = self.is_enabled()
|
||||
if enabled:
|
||||
self.gui.expert_mode = True
|
||||
self.gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Request')])
|
||||
else:
|
||||
self.gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Tx')])
|
||||
|
||||
self.toggle_QR_window(enabled)
|
||||
|
||||
|
||||
def do_enable(gui, enabled):
|
||||
if enabled:
|
||||
gui.expert_mode = True
|
||||
gui.set_hook('item_changed', item_changed)
|
||||
gui.set_hook('current_item_changed', recv_changed)
|
||||
gui.set_hook('receive_menu', receive_menu)
|
||||
gui.set_hook('update_receive_item', update_receive_item)
|
||||
gui.set_hook('timer_actions', timer_actions)
|
||||
gui.set_hook('close_main_window', close_main_window)
|
||||
gui.set_hook('init', update_gui)
|
||||
else:
|
||||
gui.unset_hook('item_changed', item_changed)
|
||||
gui.unset_hook('current_item_changed', recv_changed)
|
||||
gui.unset_hook('receive_menu', receive_menu)
|
||||
gui.unset_hook('update_receive_item', update_receive_item)
|
||||
gui.unset_hook('timer_actions', timer_actions)
|
||||
gui.unset_hook('close_main_window', close_main_window)
|
||||
gui.unset_hook('init', update_gui)
|
||||
def close_main_window(self):
|
||||
if self.qr_window:
|
||||
self.qr_window.close()
|
||||
self.qr_window = None
|
||||
|
||||
|
||||
def timer_actions(self):
|
||||
if self.qr_window:
|
||||
self.qr_window.qrw.update_qr()
|
||||
|
||||
|
||||
def toggle_QR_window(self, show):
|
||||
if show and not self.qr_window:
|
||||
self.qr_window = QR_Window(self.gui.exchanger)
|
||||
self.qr_window.setVisible(True)
|
||||
self.qr_window_geometry = self.qr_window.geometry()
|
||||
item = self.gui.receive_list.currentItem()
|
||||
if item:
|
||||
address = str(item.text(1))
|
||||
label = self.gui.wallet.labels.get(address)
|
||||
amount, currency = self.requested_amounts.get(address, (None, None))
|
||||
self.qr_window.set_content( address, label, amount, currency )
|
||||
|
||||
elif show and self.qr_window and not self.qr_window.isVisible():
|
||||
self.qr_window.setVisible(True)
|
||||
self.qr_window.setGeometry(self.qr_window_geometry)
|
||||
|
||||
elif not show and self.qr_window and self.qr_window.isVisible():
|
||||
self.qr_window_geometry = self.qr_window.geometry()
|
||||
self.qr_window.setVisible(False)
|
||||
|
||||
|
||||
|
||||
def update_gui(gui):
|
||||
enabled = is_enabled()
|
||||
if enabled:
|
||||
gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Request')])
|
||||
else:
|
||||
gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Tx')])
|
||||
|
||||
toggle_QR_window(gui, enabled)
|
||||
|
||||
|
||||
|
||||
def toggle_QR_window(self, show):
|
||||
if show and not self.qr_window:
|
||||
self.qr_window = QR_Window(self.exchanger)
|
||||
self.qr_window.setVisible(True)
|
||||
self.qr_window_geometry = self.qr_window.geometry()
|
||||
item = self.receive_list.currentItem()
|
||||
if item:
|
||||
address = str(item.text(1))
|
||||
label = self.wallet.labels.get(address)
|
||||
def update_receive_item(self, address, item):
|
||||
try:
|
||||
amount, currency = self.requested_amounts.get(address, (None, None))
|
||||
except:
|
||||
print "cannot get requested amount", address, self.requested_amounts.get(address)
|
||||
amount, currency = None, None
|
||||
self.requested_amounts.pop(address)
|
||||
|
||||
amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
|
||||
item.setData(column_index,0,amount_str)
|
||||
|
||||
|
||||
|
||||
def current_item_changed(self, a):
|
||||
if a is not None and self.qr_window and self.qr_window.isVisible():
|
||||
address = str(a.text(0))
|
||||
label = self.gui.wallet.labels.get(address)
|
||||
try:
|
||||
amount, currency = self.requested_amounts.get(address, (None, None))
|
||||
except:
|
||||
amount, currency = None, None
|
||||
self.qr_window.set_content( address, label, amount, currency )
|
||||
|
||||
elif show and self.qr_window and not self.qr_window.isVisible():
|
||||
self.qr_window.setVisible(True)
|
||||
self.qr_window.setGeometry(self.qr_window_geometry)
|
||||
|
||||
elif not show and self.qr_window and self.qr_window.isVisible():
|
||||
self.qr_window_geometry = self.qr_window.geometry()
|
||||
self.qr_window.setVisible(False)
|
||||
|
||||
|
||||
|
||||
|
||||
def item_changed(self, item, column):
|
||||
if column == column_index:
|
||||
def item_changed(self, item, column):
|
||||
if column != column_index:
|
||||
return
|
||||
address = str( item.text(0) )
|
||||
text = str( item.text(column) )
|
||||
try:
|
||||
seq = self.wallet.get_address_index(address)
|
||||
seq = self.gui.wallet.get_address_index(address)
|
||||
index = seq[-1]
|
||||
except:
|
||||
print "cannot get index"
|
||||
|
@ -198,9 +194,9 @@ def item_changed(self, item, column):
|
|||
currency = currency.upper()
|
||||
|
||||
self.requested_amounts[address] = (amount, currency)
|
||||
self.wallet.config.set_key('requested_amounts', self.requested_amounts, True)
|
||||
self.gui.wallet.config.set_key('requested_amounts', self.requested_amounts, True)
|
||||
|
||||
label = self.wallet.labels.get(address)
|
||||
label = self.gui.wallet.labels.get(address)
|
||||
if label is None:
|
||||
label = self.merchant_name + ' - %04d'%(index+1)
|
||||
self.wallet.labels[address] = label
|
||||
|
@ -213,50 +209,20 @@ def item_changed(self, item, column):
|
|||
if address in self.requested_amounts:
|
||||
self.requested_amounts.pop(address)
|
||||
|
||||
self.update_receive_item(self.receive_list.currentItem())
|
||||
|
||||
|
||||
def recv_changed(self, a):
|
||||
if a is not None and self.qr_window and self.qr_window.isVisible():
|
||||
address = str(a.text(0))
|
||||
label = self.wallet.labels.get(address)
|
||||
try:
|
||||
amount, currency = self.requested_amounts.get(address, (None, None))
|
||||
except:
|
||||
amount, currency = None, None
|
||||
self.qr_window.set_content( address, label, amount, currency )
|
||||
self.gui.update_receive_item(self.gui.receive_list.currentItem())
|
||||
|
||||
|
||||
|
||||
def edit_amount(self):
|
||||
l = self.receive_list
|
||||
item = l.currentItem()
|
||||
item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
|
||||
l.editItem( item, column_index )
|
||||
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
|
||||
|
||||
def receive_menu(self, menu):
|
||||
menu.addAction(_("Request amount"), lambda: edit_amount(self))
|
||||
def edit_amount(self):
|
||||
l = self.gui.receive_list
|
||||
item = l.currentItem()
|
||||
item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
|
||||
l.editItem( item, column_index )
|
||||
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
|
||||
|
||||
|
||||
def update_receive_item(self, address, item):
|
||||
try:
|
||||
amount, currency = self.requested_amounts.get(address, (None, None))
|
||||
except:
|
||||
print "cannot get requested amount", address, self.requested_amounts.get(address)
|
||||
amount, currency = None, None
|
||||
self.requested_amounts.pop(address)
|
||||
|
||||
amount_str = amount + (' ' + currency if currency else '') if amount is not None else ''
|
||||
item.setData(column_index,0,amount_str)
|
||||
def receive_menu(self, menu):
|
||||
menu.addAction(_("Request amount"), self.edit_amount)
|
||||
|
||||
|
||||
def close_main_window(self):
|
||||
if self.qr_window:
|
||||
self.qr_window.close()
|
||||
self.qr_window = None
|
||||
|
||||
|
||||
def timer_actions(self):
|
||||
if self.qr_window:
|
||||
self.qr_window.qrw.update_qr()
|
||||
|
|
|
@ -1,59 +1,68 @@
|
|||
from electrum.util import print_error
|
||||
from urlparse import urlparse, parse_qs
|
||||
from PyQt4.QtGui import QPushButton
|
||||
from electrum_gui.i18n import _
|
||||
|
||||
try:
|
||||
import zbar
|
||||
except ImportError:
|
||||
zbar = None
|
||||
|
||||
from electrum_gui import BasePlugin
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
def __init__(self, gui):
|
||||
BasePlugin.__init__(self, gui, 'qrscans', 'QR scans', "QR Scans.\nInstall the zbar package to enable this plugin")
|
||||
|
||||
def is_available(self):
|
||||
if not zbar:
|
||||
return False
|
||||
try:
|
||||
proc = zbar.Processor()
|
||||
proc.init()
|
||||
except zbar.SystemError:
|
||||
# Cannot open video device
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def init(gui):
|
||||
if is_enabled():
|
||||
gui.set_hook('create_send_tab', create_send_tab)
|
||||
else:
|
||||
gui.unset_hook('create_send_tab', create_send_tab)
|
||||
|
||||
def get_info():
|
||||
return 'QR scans', "QR Scans.\nInstall the zbar package to enable this plugin"
|
||||
|
||||
def is_enabled():
|
||||
return is_available()
|
||||
|
||||
def toggle(gui):
|
||||
return is_enabled()
|
||||
def create_send_tab(self, grid):
|
||||
b = QPushButton(_("Scan QR code"))
|
||||
b.clicked.connect(self.fill_from_qr)
|
||||
grid.addWidget(b, 1, 5)
|
||||
|
||||
|
||||
def is_available():
|
||||
if not zbar:
|
||||
return False
|
||||
|
||||
try:
|
||||
def scan_qr(self):
|
||||
proc = zbar.Processor()
|
||||
proc.init()
|
||||
except zbar.SystemError:
|
||||
# Cannot open video device
|
||||
return False
|
||||
proc.visible = True
|
||||
|
||||
return True
|
||||
while True:
|
||||
try:
|
||||
proc.process_one()
|
||||
except:
|
||||
# User closed the preview window
|
||||
return {}
|
||||
|
||||
def scan_qr():
|
||||
proc = zbar.Processor()
|
||||
proc.init()
|
||||
proc.visible = True
|
||||
for r in proc.results:
|
||||
if str(r.type) != 'QRCODE':
|
||||
continue
|
||||
return parse_uri(r.data)
|
||||
|
||||
|
||||
def fill_from_qr(self):
|
||||
qrcode = self.scan_qr()
|
||||
if 'address' in qrcode:
|
||||
self.gui.payto_e.setText(qrcode['address'])
|
||||
if 'amount' in qrcode:
|
||||
self.gui.amount_e.setText(str(qrcode['amount']))
|
||||
if 'label' in qrcode:
|
||||
self.gui.message_e.setText(qrcode['label'])
|
||||
if 'message' in qrcode:
|
||||
self.gui.message_e.setText("%s (%s)" % (self.gui.message_e.text(), qrcode['message']))
|
||||
|
||||
while True:
|
||||
try:
|
||||
proc.process_one()
|
||||
except:
|
||||
# User closed the preview window
|
||||
return {}
|
||||
|
||||
for r in proc.results:
|
||||
if str(r.type) != 'QRCODE':
|
||||
continue
|
||||
|
||||
return parse_uri(r.data)
|
||||
|
||||
def parse_uri(uri):
|
||||
if ':' not in uri:
|
||||
|
@ -80,24 +89,6 @@ def parse_uri(uri):
|
|||
|
||||
|
||||
|
||||
def fill_from_qr(self):
|
||||
qrcode = qrscanner.scan_qr()
|
||||
if 'address' in qrcode:
|
||||
self.payto_e.setText(qrcode['address'])
|
||||
if 'amount' in qrcode:
|
||||
self.amount_e.setText(str(qrcode['amount']))
|
||||
if 'label' in qrcode:
|
||||
self.message_e.setText(qrcode['label'])
|
||||
if 'message' in qrcode:
|
||||
self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
|
||||
|
||||
|
||||
def create_send_tab(gui, grid):
|
||||
if qrscanner.is_available():
|
||||
b = QPushButton(_("Scan QR code"))
|
||||
b.clicked.connect(lambda: fill_from_qr(gui))
|
||||
grid.addWidget(b, 1, 5)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
from PyQt4.QtGui import *
|
||||
from electrum_gui import BasePlugin
|
||||
from electrum_gui.i18n import _
|
||||
|
||||
class Plugin(BasePlugin):
|
||||
|
||||
|
||||
def __init__(self, gui):
|
||||
BasePlugin.__init__(self, gui, 'virtualkeyboard', 'Virtual Keyboard',
|
||||
_("Add an optional, mouse keyboard to the password dialog.\nWarning: do not use this if it makes you pick a weaker password."))
|
||||
self.vkb = None
|
||||
self.vkb_index = 0
|
||||
|
||||
|
||||
def password_dialog(self, pw, grid, pos):
|
||||
vkb_button = QPushButton(_("+"))
|
||||
vkb_button.setFixedWidth(20)
|
||||
vkb_button.clicked.connect(lambda: self.toggle_vkb(grid, pw))
|
||||
grid.addWidget(vkb_button, pos, 2)
|
||||
self.kb_pos = 2
|
||||
|
||||
|
||||
def toggle_vkb(self, grid, pw):
|
||||
if self.vkb: grid.removeItem(self.vkb)
|
||||
self.vkb = self.virtual_keyboard(self.vkb_index, pw)
|
||||
grid.addLayout(self.vkb, self.kb_pos, 0, 1, 3)
|
||||
self.vkb_index += 1
|
||||
|
||||
|
||||
def virtual_keyboard(self, i, pw):
|
||||
import random
|
||||
i = i%3
|
||||
if i == 0:
|
||||
chars = 'abcdefghijklmnopqrstuvwxyz '
|
||||
elif i == 1:
|
||||
chars = 'ABCDEFGHIJKLMNOPQRTSUVWXYZ '
|
||||
elif i == 2:
|
||||
chars = '1234567890!?.,;:/%&()[]{}+-'
|
||||
|
||||
n = len(chars)
|
||||
s = []
|
||||
for i in xrange(n):
|
||||
while True:
|
||||
k = random.randint(0,n-1)
|
||||
if k not in s:
|
||||
s.append(k)
|
||||
break
|
||||
|
||||
def add_target(t):
|
||||
return lambda: pw.setText(str( pw.text() ) + t)
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
grid = QGridLayout()
|
||||
grid.setSpacing(2)
|
||||
for i in range(n):
|
||||
l_button = QPushButton(chars[s[i]])
|
||||
l_button.setFixedWidth(25)
|
||||
l_button.setFixedHeight(25)
|
||||
l_button.clicked.connect(add_target(chars[s[i]]) )
|
||||
grid.addWidget(l_button, i/6, i%6)
|
||||
|
||||
vbox.addLayout(grid)
|
||||
|
||||
return vbox
|
||||
|
|
@ -28,6 +28,8 @@ if sys.platform == 'darwin':
|
|||
setup_requires=['py2app'],
|
||||
app=[mainscript],
|
||||
options=dict(py2app=dict(argv_emulation=True,
|
||||
includes = ['PyQt4.QtCore','PyQt4.QtGui', 'sip'],
|
||||
packages = ['lib', 'gui', 'plugins'],
|
||||
iconfile='electrum.icns',
|
||||
resources=["data", "icons"])),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue