electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit e42cd36318c5924409adf4189bab5c06662705c7
parent e4cefa8bc5d30b5df12e1cf851380ccad0145a87
Author: ThomasV <thomasv@gitorious>
Date:   Mon, 16 Apr 2012 18:38:27 +0400

Merge branch 'master' of gitorious.org:electrum/electrum

Diffstat:
Aclient/ANDROID | 33+++++++++++++++++++++++++++++++++
Aclient/bmp.py | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mclient/electrum4a.py | 1011+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Aclient/electrum_text_320.png | 0
Mclient/gui.py | 2+-
Mclient/gui_qt.py | 55+++++++++++++++++++++++++------------------------------
Mclient/interface.py | 39+++++++++++++++++++++++----------------
Mclient/wallet.py | 8+++++---
8 files changed, 1000 insertions(+), 354 deletions(-)

diff --git a/client/ANDROID b/client/ANDROID @@ -0,0 +1,33 @@ +INSTALLATION INSTRUCTIONS FOR ANDROID + + +1. Install sl4a and py4a : you will need at least revision r5x12 of sl4a + +To install these APKs, just visit the links below with your phone and +click on the apk link, or scan the qr code + +sl4a: http://code.google.com/p/android-scripting/wiki/Unofficial +py4a: http://code.google.com/p/python-for-android/downloads/detail?name=PythonForAndroid_r5.apk + + + +2. copy the following files in /sdcard/sl4a/scripts: + +bmp.py +electrum4a.py +interface.py +mnemonic.py +msqr.py +pyqrnative.py +ripemd.py +version.py +wallet.py +aes (directory) +ecdsa (directory) + +Note: The aes and ecdsa directories are not included in the git +repository. You will have to find them on your system, or you can +find them in the distributed version, Electrum tar.gz or Electrum.zip + + +3. to run the application, open sl4a and click on electrum4a.py diff --git a/client/bmp.py b/client/bmp.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +""" +bmp.py - module for constructing simple BMP graphics files + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +__version__ = "0.3" +__about = "bmp module, version %s, written by Paul McGuire, October, 2003, updated by Margus Laak, September, 2009" % __version__ + +from math import ceil, hypot + + +def shortToString(i): + hi = (i & 0xff00) >> 8 + lo = i & 0x00ff + return chr(lo) + chr(hi) + +def longToString(i): + hi = (long(i) & 0x7fff0000) >> 16 + lo = long(i) & 0x0000ffff + return shortToString(lo) + shortToString(hi) + +def long24ToString(i): + return chr(i & 0xff) + chr(i >> 8 & 0xff) + chr(i >> 16 & 0xff) + +def stringToLong(input_string, offset): + return ord(input_string[offset+3]) << 24 | ord(input_string[offset+2]) << 16 | ord(input_string[offset+1]) << 8 | ord(input_string[offset]) + +def stringToLong24(input_string, offset): + return ord(input_string[offset+2]) << 16 | ord(input_string[offset+1]) << 8 | ord(input_string[offset]) + +class Color(object): + """class for specifying colors while drawing BitMap elements""" + __slots__ = [ 'red', 'grn', 'blu' ] + __shade = 32 + + def __init__( self, r=0, g=0, b=0 ): + self.red = r + self.grn = g + self.blu = b + + def __setattr__(self, name, value): + if hasattr(self, name): + raise AttributeError, "Color is immutable" + else: + object.__setattr__(self, name, value) + + def __str__( self ): + return "R:%d G:%d B:%d" % (self.red, self.grn, self.blu ) + + def __hash__( self ): + return ( ( long(self.blu) ) + + ( long(self.grn) << 8 ) + + ( long(self.red) << 16 ) ) + + def __eq__( self, other ): + return (self is other) or (self.toLong == other.toLong) + + def lighten( self ): + return Color( + min( self.red + Color.__shade, 255), + min( self.grn + Color.__shade, 255), + min( self.blu + Color.__shade, 255) + ) + + def darken( self ): + return Color( + max( self.red - Color.__shade, 0), + max( self.grn - Color.__shade, 0), + max( self.blu - Color.__shade, 0) + ) + + def toLong( self ): + return self.__hash__() + + def fromLong( l ): + b = l & 0xff + l = l >> 8 + g = l & 0xff + l = l >> 8 + r = l & 0xff + return Color( r, g, b ) + fromLong = staticmethod(fromLong) + +# define class constants for common colors +Color.BLACK = Color( 0, 0, 0 ) +Color.RED = Color( 255, 0, 0 ) +Color.GREEN = Color( 0, 255, 0 ) +Color.BLUE = Color( 0, 0, 255 ) +Color.CYAN = Color( 0, 255, 255 ) +Color.MAGENTA = Color( 255, 0, 255 ) +Color.YELLOW = Color( 255, 255, 0 ) +Color.WHITE = Color( 255, 255, 255 ) +Color.DKRED = Color( 128, 0, 0 ) +Color.DKGREEN = Color( 0, 128, 0 ) +Color.DKBLUE = Color( 0, 0, 128 ) +Color.TEAL = Color( 0, 128, 128 ) +Color.PURPLE = Color( 128, 0, 128 ) +Color.BROWN = Color( 128, 128, 0 ) +Color.GRAY = Color( 128, 128, 128 ) + + +class BitMap(object): + """class for drawing and saving simple Windows bitmap files""" + + LINE_SOLID = 0 + LINE_DASHED = 1 + LINE_DOTTED = 2 + LINE_DOT_DASH=3 + _DASH_LEN = 12.0 + _DOT_LEN = 6.0 + _DOT_DASH_LEN = _DOT_LEN + _DASH_LEN + + def __init__( self, width, height, + bkgd = Color.WHITE, frgd = Color.BLACK ): + self.wd = int( ceil(width) ) + self.ht = int( ceil(height) ) + self.bgcolor = 0 + self.fgcolor = 1 + self.palette = [] + self.palette.append( bkgd.toLong() ) + self.palette.append( frgd.toLong() ) + self.currentPen = self.fgcolor + + tmparray = [ self.bgcolor ] * self.wd + self.bitarray = [ tmparray[:] for i in range( self.ht ) ] + self.currentPen = 1 + + + def plotPoint( self, x, y ): + if ( 0 <= x < self.wd and 0 <= y < self.ht ): + x = int(x) + y = int(y) + self.bitarray[y][x] = self.currentPen + + + def _saveBitMapNoCompression( self ): + line_padding = (4 - (self.wd % 4)) % 4 + + # write bitmap header + _bitmap = "BM" + _bitmap += longToString( 54 + self.ht*(self.wd*3 + line_padding) ) # DWORD size in bytes of the file + _bitmap += longToString( 0 ) # DWORD 0 + _bitmap += longToString( 54 ) + _bitmap += longToString( 40 ) # DWORD header size = 40 + _bitmap += longToString( self.wd ) # DWORD image width + _bitmap += longToString( self.ht ) # DWORD image height + _bitmap += shortToString( 1 ) # WORD planes = 1 + _bitmap += shortToString( 24 ) # WORD bits per pixel = 8 + _bitmap += longToString( 0 ) # DWORD compression = 0 + _bitmap += longToString( self.ht * (self.wd * 3 + line_padding) ) # DWORD sizeimage = size in bytes of the bitmap = width * height + _bitmap += longToString( 0 ) # DWORD horiz pixels per meter (?) + _bitmap += longToString( 0 ) # DWORD ver pixels per meter (?) + _bitmap += longToString( 0 ) # DWORD number of colors used = 256 + _bitmap += longToString( 0 ) # DWORD number of "import colors = len( self.palette ) + + # write pixels + self.bitarray.reverse() + for row in self.bitarray: + for pixel in row: + c = self.palette[pixel] + _bitmap += long24ToString(c) + for i in range(line_padding): + _bitmap += chr( 0 ) + + return _bitmap + + + + def saveFile( self, filename): + _b = self._saveBitMapNoCompression( ) + + f = file(filename, 'wb') + f.write(_b) + f.close() + + + + + + +if __name__ == "__main__": + + bmp = BitMap( 10, 10 ) + bmp.plotPoint( 5, 5 ) + bmp.plotPoint( 0, 0 ) + bmp.saveFile( "test.bmp" ) + diff --git a/client/electrum4a.py b/client/electrum4a.py @@ -18,141 +18,237 @@ + import android from interface import WalletSynchronizer from wallet import Wallet from wallet import format_satoshis from decimal import Decimal - +import mnemonic import datetime -droid = android.Android() -wallet = Wallet() -wallet.set_path("/sdcard/electrum.dat") -wallet.read() - -def get_history_layout(n): - rows = "" - for line in wallet.get_tx_history()[-n:]: - v = line['value'] - try: - dt = datetime.datetime.fromtimestamp( line['timestamp'] ) - if dt.date() == dt.today().date(): - time_str = str( dt.time() ) - else: - time_str = str( dt.date() ) - except: - print line['timestamp'] - time_str = 'pending' +def modal_dialog(title, msg = None): + droid.dialogCreateAlert(title,msg) + droid.dialogSetPositiveButtonText('OK') + droid.dialogShow() + droid.dialogGetResponse() + droid.dialogDismiss() - label = line.get('label') - #if not label: label = line['tx_hash'] - is_default_label = (label == '') or (label is None) - if is_default_label: label = line['default_label'] +def modal_input(title, msg, value = None, etype=None): + droid.dialogCreateInput(title, msg, value, etype) + droid.dialogSetPositiveButtonText('OK') + droid.dialogSetNegativeButtonText('Cancel') + droid.dialogShow() + response = droid.dialogGetResponse().result + droid.dialogDismiss() + if response.get('which') == 'positive': + return response.get('value') - rows += """ - <TableRow - android:layout_width="fill_parent"> - <TextView - android:layout_column="1" - android:text="%s" - android:padding="2px" /> - <TextView - android:text="%s" - android:padding="2px" /> - <TextView - android:text="%s" - android:gravity="right" - android:padding="2px" /> - </TableRow>"""%(time_str, ' '+ label[0:10]+ '... ', format_satoshis(v)) +def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'): + droid.dialogCreateAlert(q, msg) + droid.dialogSetPositiveButtonText(pos_text) + droid.dialogSetNegativeButtonText(neg_text) + droid.dialogShow() + response = droid.dialogGetResponse().result + droid.dialogDismiss() + return response.get('which') == 'positive' + +def edit_label(addr): + v = modal_input('Edit label',None,wallet.labels.get(addr)) + if v is not None: + if v: + wallet.labels[addr] = v + else: + if addr in wallet.labels.keys(): + wallet.labels.pop(addr) + wallet.update_tx_history() + wallet.save() + droid.fullSetProperty("labelTextView", "text", v) +def select_from_contacts(): + title = 'Contacts:' + droid.dialogCreateAlert(title) + l = [] + for i in range(len(wallet.addressbook)): + addr = wallet.addressbook[i] + label = wallet.labels.get(addr,addr) + l.append( label ) + droid.dialogSetItems(l) + droid.dialogSetPositiveButtonText('New contact') + droid.dialogShow() + response = droid.dialogGetResponse().result + droid.dialogDismiss() - output = """ -<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:stretchColumns="1"> - %s -</TableLayout> -"""% rows - return output + if response.get('which') == 'positive': + return 'newcontact' + result = response.get('item') + print result + if result is not None: + addr = wallet.addressbook[result] + return addr -def show_addresses(): +def select_from_addresses(): droid.dialogCreateAlert("Addresses:") l = [] for i in range(len(wallet.addresses)): addr = wallet.addresses[i] - l.append( wallet.labels.get(addr,'') + ' ' + addr) - + label = wallet.labels.get(addr,addr) + l.append( label ) droid.dialogSetItems(l) droid.dialogShow() - response = droid.dialogGetResponse().result + response = droid.dialogGetResponse() + result = response.result.get('item') droid.dialogDismiss() + if result is not None: + addr = wallet.addresses[result] + return addr - # show qr code - print response +def protocol_name(p): + if p == 't': return 'TCP/stratum' + if p == 'h': return 'HTTP/Stratum' + if p == 'n': return 'TCP/native' + +def protocol_dialog(host, protocol, z): + droid.dialogCreateAlert('Protocol',host) + if z: + protocols = z.keys() + else: + protocols = ['t','h','n'] + l = [] + current = protocols.index(protocol) + for p in protocols: + l.append(protocol_name(p)) + droid.dialogSetSingleChoiceItems(l, current) + droid.dialogSetPositiveButtonText('OK') + droid.dialogSetNegativeButtonText('Cancel') + droid.dialogShow() + response = droid.dialogGetResponse().result + if not response: return + if response.get('which') == 'positive': + response = droid.dialogGetSelectedItems().result[0] + droid.dialogDismiss() + p = protocols[response] + port = z[p] + return host + ':' + port + ':' + p + + + + +def make_layout(s, scrollable = False): + content = """ + + <LinearLayout + android:id="@+id/zz" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="#ff222222"> + + <TextView + android:id="@+id/textElectrum" + android:text="Electrum" + android:textSize="7pt" + android:textColor="#ff4444ff" + android:gravity="left" + android:layout_height="wrap_content" + android:layout_width="match_parent" + /> + </LinearLayout> + + %s """%s + + if scrollable: + content = """ + <ScrollView + android:id="@+id/scrollview" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + %s + + </LinearLayout> + </ScrollView> + """%content -def main_layout(): return """<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/background" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="#ff000000"> + android:background="#ff000022"> + + %s + </LinearLayout>"""%content + + + + +def main_layout(): + return make_layout(""" + <TextView android:id="@+id/balanceTextView" + android:layout_width="match_parent" + android:text="" + android:textColor="#ffffffff" + android:textAppearance="?android:attr/textAppearanceLarge" + android:padding="7dip" + android:textSize="8pt" + android:gravity="center_vertical|center_horizontal|left"> + </TextView> <TextView android:id="@+id/historyTextView" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Electrum" + android:text="Recent transactions" android:textAppearance="?android:attr/textAppearanceLarge" android:gravity="center_vertical|center_horizontal|center"> </TextView> - %s + %s """%get_history_layout(15),True) - <TextView android:id="@+id/balanceTextView" + + +def qr_layout(addr): + return make_layout(""" + + <TextView android:id="@+id/addrTextView" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="" + android:layout_height="50" + android:text="%s" android:textAppearance="?android:attr/textAppearanceLarge" - android:gravity="left"> - </TextView> - - <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" android:id="@+id/linearLayout1"> - <Button android:id="@+id/buttonSend" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Send"></Button> - <Button android:id="@+id/buttonReceive" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Receive"></Button> - <Button android:id="@+id/buttonQuit" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Quit"></Button> - </LinearLayout> + android:gravity="center_vertical|center_horizontal|center"> + </TextView> -</LinearLayout> -"""%get_history_layout(10) + <ImageView + android:id="@+id/qrView" + android:gravity="center" + android:layout_width="match_parent" + android:layout_height="350" + android:antialias="false" + android:src="file:///sdcard/sl4a/qrcode.bmp" /> + <TextView android:id="@+id/labelTextView" + android:layout_width="match_parent" + android:layout_height="50" + android:text="%s" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="center_vertical|center_horizontal|center"> + </TextView> -payto_layout="""<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/background" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="#ff000000"> + """%(addr,wallet.labels.get(addr,'')), True) - <LinearLayout android:id="@+id/linearLayout0" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="#44ffffff"> +payto_layout = make_layout(""" <TextView android:id="@+id/recipientTextView" android:layout_width="match_parent" @@ -166,21 +262,18 @@ payto_layout="""<?xml version="1.0" encoding="utf-8"?> <EditText android:id="@+id/recipient" android:layout_width="match_parent" android:layout_height="wrap_content" - android:tag="Tag Me" android:inputType="textCapWords|textPhonetic|number"> + android:tag="Tag Me" android:inputType="text"> </EditText> <LinearLayout android:id="@+id/linearLayout1" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/buttonQR" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Scan QR"></Button> + android:layout_height="wrap_content" android:text="From QR code"></Button> <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Contacts"></Button> + android:layout_height="wrap_content" android:text="From Contacts"></Button> </LinearLayout> - </LinearLayout> - - <TextView android:id="@+id/labelTextView" android:layout_width="match_parent" @@ -193,7 +286,7 @@ payto_layout="""<?xml version="1.0" encoding="utf-8"?> <EditText android:id="@+id/label" android:layout_width="match_parent" android:layout_height="wrap_content" - android:tag="Tag Me" android:inputType="textCapWords|textPhonetic|number"> + android:tag="Tag Me" android:inputType="text"> </EditText> <TextView android:id="@+id/amountLabelTextView" @@ -214,201 +307,318 @@ payto_layout="""<?xml version="1.0" encoding="utf-8"?> android:layout_height="wrap_content" android:id="@+id/linearLayout1"> <Button android:id="@+id/buttonPay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send"></Button> - <Button android:id="@+id/buttonCancelSend" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Cancel"></Button> - </LinearLayout> -</LinearLayout> -""" + </LinearLayout>""",False) -settings_layout = """<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/background" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="#ff000000"> - <TextView android:id="@+id/serverTextView" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Server:" - android:textAppearance="?android:attr/textAppearanceLarge" - android:gravity="left"> - </TextView> +settings_layout = make_layout(""" <ListView + android:id="@+id/myListView" + android:layout_width="match_parent" + android:layout_height="wrap_content" />""") - <EditText android:id="@+id/server" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:tag="Tag Me" android:inputType="*"> - </EditText> - <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" android:id="@+id/linearLayout1"> - <Button android:id="@+id/buttonServer" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Server List"></Button> - <Button android:id="@+id/buttonSave" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Save"></Button> - <Button android:id="@+id/buttonCancel" android:layout_width="wrap_content" - android:layout_height="wrap_content" android:text="Cancel"></Button> - </LinearLayout> -</LinearLayout> -""" +def get_history_values(n): + values = [] + h = wallet.get_tx_history() + length = min(n, len(h)) + for i in range(length): + line = h[-i-1] + v = line['value'] + try: + dt = datetime.datetime.fromtimestamp( line['timestamp'] ) + if dt.date() == dt.today().date(): + time_str = str( dt.time() ) + else: + time_str = str( dt.date() ) + conf = 'v' + except: + print line['timestamp'] + time_str = 'pending' + conf = 'o' + tx_hash = line['tx_hash'] + label = wallet.labels.get(tx_hash) + is_default_label = (label == '') or (label is None) + if is_default_label: label = line['default_label'] + values.append((conf, ' ' + time_str, ' ' + format_satoshis(v,True), ' ' + label )) + return values -def show_balance(): - c, u = wallet.get_balance() - droid.fullSetProperty("balanceTextView","text","Balance:"+format_satoshis(c)) + +def get_history_layout(n): + rows = "" + i = 0 + values = get_history_values(n) + for v in values: + a,b,c,d = v + color = "#ff00ff00" if a == 'v' else "#ffff0000" + rows += """ + <TableRow> + <TextView + android:id="@+id/hl_%d_col1" + android:layout_column="0" + android:text="%s" + android:textColor="%s" + android:padding="3" /> + <TextView + android:id="@+id/hl_%d_col2" + android:layout_column="1" + android:text="%s" + android:padding="3" /> + <TextView + android:id="@+id/hl_%d_col3" + android:layout_column="2" + android:text="%s" + android:padding="3" /> + <TextView + android:id="@+id/hl_%d_col4" + android:layout_column="3" + android:text="%s" + android:padding="4" /> + </TableRow>"""%(i,a,color,i,b,i,c,i,d) + i += 1 + + output = """ +<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:stretchColumns="0,1,2,3"> + %s +</TableLayout>"""% rows + return output + + +def set_history_layout(n): + values = get_history_values(n) + i = 0 + for v in values: + a,b,c,d = v + droid.fullSetProperty("hl_%d_col1"%i,"text", a) + + if a == 'v': + droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00") + else: + droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000") + + droid.fullSetProperty("hl_%d_col2"%i,"text", b) + droid.fullSetProperty("hl_%d_col3"%i,"text", c) + droid.fullSetProperty("hl_%d_col4"%i,"text", d) + i += 1 + + + + +status_text = '' +def update_layout(): + global status_text + if not wallet.interface.is_connected: + text = "Not connected..." + elif wallet.blocks == 0: + text = "Server not ready" + elif not wallet.up_to_date: + text = "Synchronizing..." + else: + c, u = wallet.get_balance() + text = "Balance:"+format_satoshis(c) + if u : text += ' [' + format_satoshis(u,True).strip() + ']' + + + # vibrate if status changed + if text != status_text: + if status_text and wallet.interface.is_connected and wallet.up_to_date: + droid.vibrate() + status_text = text + + droid.fullSetProperty("balanceTextView", "text", status_text) + + if wallet.up_to_date: + set_history_layout(15) -def recipient_dialog(): - title = 'Pay to:' - message = ('Select recipient') - droid.dialogCreateAlert(title, message) - droid.dialogSetItems(wallet.addressbook) - droid.dialogShow() - response = droid.dialogGetResponse() - result = response.result.get('item') - droid.dialogDismiss() - if result is not None: - addr = wallet.addressbook[result] - return addr def pay_to(recipient, amount, fee, label): if wallet.use_encryption: password = droid.dialogGetPassword('Password').result - print "password", password + if not password: return else: password = None droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...") droid.dialogShow() - tx = wallet.mktx( recipient, amount, label, password, fee) - print tx - droid.dialogDismiss() - if tx: - r, h = wallet.sendtx( tx ) - droid.dialogCreateAlert('tx sent', h) - droid.dialogSetPositiveButtonText('OK') - droid.dialogShow() - response = droid.dialogGetResponse().result + try: + tx = wallet.mktx( recipient, amount, label, password, fee) + except BaseException, e: + modal_dialog('error', e.message) droid.dialogDismiss() - return h - else: - return 'error' - + return + droid.dialogDismiss() + r, h = wallet.sendtx( tx ) + if r: + modal_dialog('Payment sent', h) + return True + else: + modal_dialog('Error', h) -if not wallet.file_exists: - droid.dialogCreateAlert("wallet file not found") - droid.dialogSetPositiveButtonText('OK') - droid.dialogShow() - resp = droid.dialogGetResponse().result - print resp - code = droid.scanBarcode() - r = code.result - if r: - seed = r['extras']['SCAN_RESULT'] - else: - exit(1) +def recover(): - droid.dialogCreateAlert('seed', seed) - droid.dialogSetPositiveButtonText('OK') + droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?") + droid.dialogSetPositiveButtonText('Create') + droid.dialogSetNeutralButtonText('Restore') droid.dialogSetNegativeButtonText('Cancel') droid.dialogShow() response = droid.dialogGetResponse().result droid.dialogDismiss() - print response + if response.get('which') == 'negative': + exit(1) - wallet.seed = str(seed) - wallet.init_mpk( wallet.seed ) - droid.dialogCreateSpinnerProgress("Electrum", "recovering keys") + is_recovery = response.get('which') == 'neutral' + + if not is_recovery: + wallet.new_seed(None) + else: + if modal_question("Input method",None,'QR Code', 'mnemonic'): + code = droid.scanBarcode() + r = code.result + if r: + seed = r['extras']['SCAN_RESULT'] + else: + exit(1) + else: + m = modal_input('Mnemonic','please enter your code') + try: + seed = mnemonic.mn_decode(m.split(' ')) + except: + modal_dialog('error: could not decode this seed') + exit(1) + + wallet.seed = str(seed) + + modal_dialog('Your seed is:', wallet.seed) + modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(wallet.seed)) ) + + msg = "recovering wallet..." if is_recovery else "creating wallet..." + droid.dialogCreateSpinnerProgress("Electrum", msg) droid.dialogShow() + + wallet.init_mpk( wallet.seed ) WalletSynchronizer(wallet,True).start() wallet.update() - wallet.save() + droid.dialogDismiss() droid.vibrate() - if wallet.is_found(): - # history and addressbook - wallet.update_tx_history() - wallet.fill_addressbook() - droid.dialogCreateAlert("recovery successful") - droid.dialogShow() - wallet.save() - else: - droid.dialogCreateSpinnerProgress("wallet not found") - droid.dialogShow() - exit(1) + if is_recovery: + if wallet.is_found(): + wallet.update_tx_history() + wallet.fill_addressbook() + modal_dialog("recovery successful") + else: + if not modal_question("no transactions found for this seed","do you want to keep this wallet?"): + exit(1) -else: - droid.dialogCreateSpinnerProgress("Electrum", "synchronizing") - droid.dialogShow() - WalletSynchronizer(wallet,True).start() - wallet.update() + change_password_dialog() wallet.save() - droid.dialogDismiss() - droid.vibrate() -def add_menu(): - droid.addOptionsMenuItem("Settings","settings",None,"") - droid.addOptionsMenuItem("Quit","quit",None,"") -add_menu() +def make_new_contact(): + code = droid.scanBarcode() + r = code.result + if r: + address = r['extras']['SCAN_RESULT'] + if address: + if wallet.is_valid(address): + if modal_question('Add to contacts?', address): + wallet.addressbook.append(address) + wallet.save() + else: + modal_dialog('Invalid address', address) + + +do_refresh = False +def update_callback(): + global do_refresh + print "gui callback", wallet.interface.is_connected, wallet.up_to_date + do_refresh = True + droid.eventPost("refresh",'z') def main_loop(): - droid.fullShow(main_layout()) - show_balance() + global do_refresh + + update_layout() out = None + quitting = False while out is None: - event = droid.eventWait().result - print "got event in main loop", event + event = droid.eventWait(1000).result + if event is None: + if do_refresh: + update_layout() + do_refresh = False + continue - if event["name"]=="click": - id=event["data"]["id"] - if id=="buttonQuit": - out = 'exit' + print "got event in main loop", repr(event) + if event == 'OK': continue + if event is None: continue + #if event["name"]=="refresh": - elif id=="buttonSend": - out = 'payto' - elif id=="buttonReceive": - show_addresses() + # request 2 taps before we exit + if event["name"]=="key": + if event["data"]["key"] == '4': + if quitting: + out = 'quit' + else: + quitting = True + else: quitting = False - elif id=="buttonQuit": - out = 'quit' + if event["name"]=="click": + id=event["data"]["id"] elif event["name"]=="settings": out = 'settings' - elif event["name"]=="quit": - out = 'quit' + elif event["name"] in menu_commands: + out = event["name"] + + if out == 'contacts': + global contact_addr + contact_addr = select_from_contacts() + if contact_addr == 'newcontact': + make_new_contact() + contact_addr = None + if not contact_addr: + out = None + + elif out == "receive": + global receive_addr + receive_addr = select_from_addresses() + if not receive_addr: + out = None - # print droid.fullSetProperty("background","backgroundColor","0xff7f0000") - # elif event["name"]=="screen": - # if event["data"]=="destroy": - # out = 'exit' return out + def payto_loop(): - droid.fullShow(payto_layout) + global recipient + if recipient: + droid.fullSetProperty("recipient","text",recipient) + recipient = None + out = None while out is None: event = droid.eventWait().result @@ -423,20 +633,23 @@ def payto_loop(): recipient = droid.fullQueryDetail("recipient").result.get('text') label = droid.fullQueryDetail("label").result.get('text') amount = droid.fullQueryDetail('amount').result.get('text') - fee = '0.001' - amount = int( 100000000 * Decimal(amount) ) - fee = int( 100000000 * Decimal(fee) ) - result = pay_to(recipient, amount, fee, label) - - droid.dialogCreateAlert('result', result) - droid.dialogSetPositiveButtonText('OK') - droid.dialogShow() - droid.dialogGetResponse() - droid.dialogDismiss() - out = 'main' + + if not wallet.is_valid(recipient): + modal_dialog('Error','Invalid Bitcoin address') + continue + + try: + amount = int( 100000000 * Decimal(amount) ) + except: + modal_dialog('Error','Invalid amount') + continue + + result = pay_to(recipient, amount, wallet.fee, label) + if result: + out = 'main' elif id=="buttonContacts": - addr = recipient_dialog() + addr = select_from_contacts() droid.fullSetProperty("recipient","text",addr) elif id=="buttonQR": @@ -447,14 +660,12 @@ def payto_loop(): if addr: droid.fullSetProperty("recipient","text",addr) - elif id=="buttonCancelSend": - out = 'main' + elif event["name"] in menu_commands: + out = event["name"] - elif event["name"]=="settings": - out = 'settings' - - elif event["name"]=="quit": - out = 'quit' + elif event["name"]=="key": + if event["data"]["key"] == '4': + out = 'main' #elif event["name"]=="screen": # if event["data"]=="destroy": @@ -463,116 +674,308 @@ def payto_loop(): return out -def history_loop(): - layout = get_history_layout() - droid.fullShow(layout) +receive_addr = '' +contact_addr = '' +recipient = '' + +def receive_loop(): out = None while out is None: event = droid.eventWait().result - print "got event in history loop", event - if event["name"] == "click": - - if event["data"]["text"] == "OK": + print "got event", event + if event["name"]=="key": + if event["data"]["key"] == '4': out = 'main' - elif event["name"] == "key": - print repr(event["data"]["key"]) + elif event["name"]=="clipboard": + droid.setClipboard(receive_addr) + modal_dialog('Address copied to clipboard',receive_addr) + + elif event["name"]=="edit": + edit_label(receive_addr) + + return out + +def contacts_loop(): + global recipient + out = None + while out is None: + event = droid.eventWait().result + print "got event", event + if event["name"]=="key": if event["data"]["key"] == '4': out = 'main' - #elif event["name"]=="screen": - # if event["data"]=="destroy": - # out = 'main' + elif event["name"]=="clipboard": + droid.setClipboard(contact_addr) + modal_dialog('Address copied to clipboard',contact_addr) + + elif event["name"]=="edit": + edit_label(contact_addr) + + elif event["name"]=="paytocontact": + recipient = contact_addr + out = 'send' + + elif event["name"]=="deletecontact": + if modal_question('delete contact', contact_addr): + out = 'main' return out + def server_dialog(plist): - droid.dialogCreateAlert("servers") + droid.dialogCreateAlert("Public servers") droid.dialogSetItems( plist.keys() ) + droid.dialogSetPositiveButtonText('Private server') droid.dialogShow() - i = droid.dialogGetResponse().result.get('item') + response = droid.dialogGetResponse().result droid.dialogDismiss() + + if response.get('which') == 'positive': + return modal_input('Private server', None) + + i = response.get('item') if i is not None: response = plist.keys()[i] return response -def protocol_dialog(plist): - options=["TCP","HTTP","native"] - droid.dialogCreateAlert("Protocol") - droid.dialogSetSingleChoiceItems(options) +def seed_dialog(): + if wallet.use_encryption: + password = droid.dialogGetPassword('Seed').result + if not password: return + else: + password = None + + try: + seed = wallet.pw_decode( wallet.seed, password) + except: + modal_dialog('error','incorrect password') + return + + modal_dialog('Your seed is',seed) + modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(seed)) ) + +def change_password_dialog(): + if wallet.use_encryption: + password = droid.dialogGetPassword('Your wallet is encrypted').result + if password is None: return + else: + password = None + + try: + seed = wallet.pw_decode( wallet.seed, password) + except: + modal_dialog('error','incorrect password') + return + + new_password = droid.dialogGetPassword('Choose a password').result + if new_password == None: + return + + if new_password != '': + password2 = droid.dialogGetPassword('Confirm new password').result + if new_password != password2: + modal_dialog('error','passwords do not match') + return + + wallet.update_password(seed, new_password) + if new_password: + modal_dialog('Password updated','your wallet is encrypted') + else: + modal_dialog('No password','your wallet is not encrypted') + return True def settings_loop(): - droid.fullShow(settings_layout) - droid.fullSetProperty("server","text",wallet.server) - out = None - while out is None: - event = droid.eventWait().result - if event["name"] == "click": - id = event["data"]["id"] + def set_listview(): + server, port, p = wallet.server.split(':') + fee = str( Decimal( wallet.fee)/100000000 ) + is_encrypted = 'yes' if wallet.use_encryption else 'no' + protocol = protocol_name(p) + droid.fullShow(settings_layout) + droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed']) - if id=="buttonServer": - plist = {} - for item in wallet.interface.servers: - host, pp = item - z = {} - for item2 in pp: - protocol, port = item2 - z[protocol] = port - plist[host] = z + set_listview() + out = None + while out is None: + event = droid.eventWait().result + print "got event", event + if event == 'OK': continue + if not event: continue + + plist = {} + for item in wallet.interface.servers: + host, pp = item + z = {} + for item2 in pp: + protocol, port = item2 + z[protocol] = port + plist[host] = z + + if event["name"] == "itemclick": + pos = event["data"]["position"] + host, port, protocol = wallet.server.split(':') + + if pos == "0": #server host = server_dialog(plist) - p = plist[host] - port = p['t'] - srv = host + ':' + port + ':t' - droid.fullSetProperty("server","text",srv) - - elif id=="buttonSave": - droid.fullQuery() - srv = droid.fullQueryDetail("server").result.get('text') - try: - wallet.set_server(srv) - out = 'main' - except: - droid.dialogCreateAlert('error') - droid.dialogSetPositiveButtonText('OK') - droid.dialogShow() - droid.dialogGetResponse() - droid.dialogDismiss() - - elif id=="buttonCancel": - out = 'main' + if host: + p = plist[host] + port = p['t'] + srv = host + ':' + port + ':t' + try: + wallet.set_server(srv) + except: + modal_dialog('error','invalid server') + set_listview() + + elif pos == "1": #protocol + if host in plist: + srv = protocol_dialog(host, protocol, plist[host]) + if srv: + try: + wallet.set_server(srv) + except: + modal_dialog('error','invalid server') + set_listview() + + elif pos == "2": #port + a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number") + if a_port: + if a_port != port: + srv = host + ':' + a_port + ':'+ protocol + try: + wallet.set_server(srv) + except: + modal_dialog('error','invalid port number') + set_listview() + + elif pos == "3": #fee + fee = modal_input('Transaction fee', 'The fee will be this amount multiplied by the number of inputs in your transaction. ', str( Decimal( wallet.fee)/100000000 ), "numberDecimal") + if fee: + try: + fee = int( 100000000 * Decimal(fee) ) + except: + modal_dialog('error','invalid fee value') + if wallet.fee != fee: + wallet.fee = fee + wallet.save() + set_listview() + + elif pos == "4": + if change_password_dialog(): + set_listview() + + elif pos == "5": + seed_dialog() + + + elif event["name"] in menu_commands: + out = event["name"] + + elif event["name"] == 'cancel': + out = 'main' elif event["name"] == "key": - print repr(event["data"]["key"]) if event["data"]["key"] == '4': out = 'main' - elif event["name"]=="quit": - out = 'quit' - return out - + + + +menu_commands = ["send", "receive", "settings", "contacts", "main"] +droid = android.Android() +wallet = Wallet(update_callback) + +wallet.set_path("/sdcard/electrum.dat") +wallet.read() +if not wallet.file_exists: + recover() +else: + WalletSynchronizer(wallet,True).start() s = 'main' + +def add_menu(s): + droid.clearOptionsMenu() + if s == 'main': + droid.addOptionsMenuItem("Send","send",None,"") + droid.addOptionsMenuItem("Receive","receive",None,"") + droid.addOptionsMenuItem("Contacts","contacts",None,"") + droid.addOptionsMenuItem("Settings","settings",None,"") + elif s == 'receive': + droid.addOptionsMenuItem("Copy","clipboard",None,"") + droid.addOptionsMenuItem("Label","edit",None,"") + elif s == 'contacts': + droid.addOptionsMenuItem("Copy","clipboard",None,"") + droid.addOptionsMenuItem("Label","edit",None,"") + droid.addOptionsMenuItem("Pay to","paytocontact",None,"") + #droid.addOptionsMenuItem("Delete","deletecontact",None,"") + +def make_bitmap(addr): + # fixme: this is highly inefficient + droid.dialogCreateSpinnerProgress("please wait") + droid.dialogShow() + import pyqrnative, bmp + qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H) + qr.addData(addr) + qr.make() + k = qr.getModuleCount() + bitmap = bmp.BitMap( 35*8, 35*8 ) + print len(bitmap.bitarray) + bitmap.bitarray = [] + assert k == 33 + + for r in range(35): + tmparray = [ 0 ] * 35*8 + + if 0 < r < 34: + for c in range(k): + if qr.isDark(r-1, c): + tmparray[ (1+c)*8:(2+c)*8] = [1]*8 + + for i in range(8): + bitmap.bitarray.append( tmparray[:] ) + + bitmap.saveFile( "/sdcard/sl4a/qrcode.bmp" ) + droid.dialogDismiss() + + + while True: + add_menu(s) if s == 'main': + droid.fullShow(main_layout()) s = main_loop() - elif s == 'payto': + #droid.fullDismiss() + + elif s == 'send': + droid.fullShow(payto_layout) s = payto_loop() - elif s == 'settings': - s = settings_loop() - elif s == 'history': - s = history_loop() + #droid.fullDismiss() + + elif s == 'receive': + make_bitmap(receive_addr) + droid.fullShow(qr_layout(receive_addr)) + s = receive_loop() + elif s == 'contacts': + make_bitmap(contact_addr) + droid.fullShow(qr_layout(contact_addr)) s = contacts_loop() + + elif s == 'settings': + #droid.fullShow(settings_layout) + s = settings_loop() + #droid.fullDismiss() else: break -droid.fullDismiss() droid.makeToast("Bye!") diff --git a/client/electrum_text_320.png b/client/electrum_text_320.png Binary files differ. diff --git a/client/gui.py b/client/gui.py @@ -1111,7 +1111,7 @@ class ElectrumWindow: self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks\nresponse time: %f"%(interface.host, interface.port, self.wallet.blocks, interface.rtime)) c, u = self.wallet.get_balance() text = "Balance: %s "%( format_satoshis(c) ) - if u: text += "[%s unconfirmed]"%( format_satoshis(u,True) ) + if u: text += "[%s unconfirmed]"%( format_satoshis(u,True).strip() ) else: self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(interface.host, self.wallet.blocks)) diff --git a/client/gui_qt.py b/client/gui_qt.py @@ -100,8 +100,6 @@ class QRCodeWidget(QWidget): super(QRCodeWidget, self).__init__() self.addr = addr self.setGeometry(300, 300, 350, 350) - self.setWindowTitle('Colors') - self.show() self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H) self.qr.addData(addr) self.qr.make() @@ -144,6 +142,8 @@ class ElectrumWindow(QMainWindow): def __init__(self, wallet): QMainWindow.__init__(self) self.wallet = wallet + self.wallet.gui_callback = self.update_callback + self.funds_error = False self.tabs = tabs = QTabWidget(self) @@ -164,11 +164,11 @@ class ElectrumWindow(QMainWindow): QShortcut(QKeySequence("Ctrl+Q"), self, self.close) QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() )) QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() )) - + + self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet) def connect_slots(self, sender): - self.connect(sender, QtCore.SIGNAL('timersignal'), self.update_wallet) self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient) self.previous_payto_e='' @@ -189,6 +189,9 @@ class ElectrumWindow(QMainWindow): self.payto_e.setText(s) + def update_callback(self): + self.emit(QtCore.SIGNAL('updatesignal')) + def update_wallet(self): if self.wallet.interface.is_connected: if self.wallet.blocks == -1: @@ -203,7 +206,7 @@ class ElectrumWindow(QMainWindow): else: c, u = self.wallet.get_balance() text = "Balance: %s "%( format_satoshis(c) ) - if u: text += "[%s unconfirmed]"%( format_satoshis(u,True) ) + if u: text += "[%s unconfirmed]"%( format_satoshis(u,True).strip() ) icon = QIcon(":icons/status_connected.png") else: text = "Not connected" @@ -215,8 +218,7 @@ class ElectrumWindow(QMainWindow): self.statusBar().showMessage(text) self.status_button.setIcon( icon ) - if self.wallet.was_updated and self.wallet.up_to_date: - self.wallet.was_updated = False + if self.wallet.up_to_date: self.textbox.setText( self.wallet.banner ) self.update_history_tab() self.update_receive_tab() @@ -522,17 +524,7 @@ class ElectrumWindow(QMainWindow): addr = unicode( i.text(0) ) return addr - def showqrcode(address): - if not address: return - d = QDialog(self) - d.setModal(1) - d.setMinimumSize(270, 300) - vbox = QVBoxLayout() - vbox.addWidget(QRCodeWidget(address)) - vbox.addLayout(ok_cancel_buttons(d)) - d.setLayout(vbox) - d.exec_() - qrButton = EnterButton("QR",lambda: showqrcode(get_addr(l))) + qrButton = EnterButton("QR",lambda: ElectrumWindow.showqrcode(get_addr(l))) def copy2clipboard(addr): self.app.clipboard().setText(addr) @@ -665,19 +657,20 @@ class ElectrumWindow(QMainWindow): + ' '.join(mnemonic.mn_encode(seed)) + "\"" QMessageBox.information(parent, 'Seed', msg, 'OK') + ElectrumWindow.showqrcode(seed) - def showqrcode(address): - if not address: return - d = QDialog(None) - d.setModal(1) - d.setMinimumSize(270, 300) - vbox = QVBoxLayout() - vbox.addWidget(QRCodeWidget(address)) - vbox.addLayout(ok_cancel_buttons(d)) - d.setLayout(vbox) - d.exec_() - showqrcode(seed) - + @staticmethod + def showqrcode(address): + if not address: return + d = QDialog(None) + d.setModal(1) + d.setWindowTitle(address) + d.setMinimumSize(270, 300) + vbox = QVBoxLayout() + vbox.addWidget(QRCodeWidget(address)) + vbox.addLayout(ok_cancel_buttons(d)) + d.setLayout(vbox) + d.exec_() def question(self, msg): return QMessageBox.question(self, 'Message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes @@ -1059,4 +1052,6 @@ class ElectrumGui(): if url: w.set_url(url) w.app = self.app w.connect_slots(s) + w.update_wallet() + self.app.exec_() diff --git a/client/interface.py b/client/interface.py @@ -316,6 +316,7 @@ class TcpStratumInterface(Interface): traceback.print_exc(file=sys.stdout) self.is_connected = False + print "poking" self.poke() def send(self, messages): @@ -448,27 +449,33 @@ class WalletSynchronizer(threading.Thread): def run(self): import socket, time while True: - try: - while self.interface.is_connected: - new_addresses = self.wallet.synchronize() - if new_addresses: - self.interface.subscribe(new_addresses) - for addr in new_addresses: - with self.wallet.lock: - self.wallet.addresses_waiting_for_status.append(addr) - - if self.wallet.is_up_to_date(): + while self.interface.is_connected: + new_addresses = self.wallet.synchronize() + if new_addresses: + self.interface.subscribe(new_addresses) + for addr in new_addresses: + with self.wallet.lock: + self.wallet.addresses_waiting_for_status.append(addr) + + if self.wallet.is_up_to_date(): + if not self.wallet.up_to_date: self.wallet.up_to_date = True + self.wallet.was_updated = True self.wallet.up_to_date_event.set() - else: + else: + if self.wallet.up_to_date: self.wallet.up_to_date = False + self.wallet.was_updated = True - response = self.interface.responses.get()#True,100000000000) # workaround so that it can be keyboard interrupted - self.handle_response(response) - except socket.error: - print "socket error" - wallet.interface.is_connected = False + if self.wallet.was_updated: + self.wallet.gui_callback() + self.wallet.was_updated = False + + response = self.interface.responses.get() + self.handle_response(response) + print "disconnected, gui callback" + self.wallet.gui_callback() if self.loop: time.sleep(5) self.start_interface() diff --git a/client/wallet.py b/client/wallet.py @@ -242,10 +242,11 @@ from interface import DEFAULT_SERVERS class Wallet: - def __init__(self): + def __init__(self, gui_callback = lambda: None): self.electrum_version = ELECTRUM_VERSION self.seed_version = SEED_VERSION + self.gui_callback = gui_callback self.gap_limit = 5 # configuration self.fee = 100000 @@ -339,7 +340,7 @@ class Wallet: def new_seed(self, password): seed = "%032x"%ecdsa.util.randrange( pow(2,128) ) - self.init_mpk(seed) + #self.init_mpk(seed) # encrypt self.seed = self.pw_encode( seed, password ) @@ -849,7 +850,8 @@ class Wallet: return target, signing_addr, auth_name def update_password(self, seed, new_password): - self.use_encryption = (new_password != '') + if new_password == '': new_password = None + self.use_encryption = (new_password != None) self.seed = self.pw_encode( seed, new_password) for k in self.imported_keys.keys(): a = self.imported_keys[k]