1 from electrum.util import print_error
2 from urlparse import urlparse, parse_qs
3 from PyQt4.QtGui import QPushButton, QMessageBox, QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel
4 from PyQt4.QtCore import Qt
6 from electrum.i18n import _
8 from electrum import Transaction
9 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
10 from electrum_gui.qt.qrcodewidget import QRCodeWidget
11 from electrum import bmp
19 from electrum import BasePlugin
20 class Plugin(BasePlugin):
22 def fullname(self): return 'QR scans'
24 def description(self): return "QR Scans.\nInstall the zbar package (http://zbar.sourceforge.net/download.html) to enable this plugin"
26 def __init__(self, gui, name):
27 BasePlugin.__init__(self, gui, name)
28 self._is_available = self._init()
34 proc = zbar.Processor()
36 except zbar.SystemError:
37 # Cannot open video device
42 def load_wallet(self, wallet):
43 b = QPushButton(_("Scan QR code"))
44 b.clicked.connect(self.fill_from_qr)
45 self.send_tab_grid.addWidget(b, 1, 5)
46 b2 = QPushButton(_("Scan TxQR"))
47 b2.clicked.connect(self.read_raw_qr)
50 b3 = QPushButton(_("Show unsigned TxQR"))
51 b3.clicked.connect(self.show_raw_qr)
52 self.send_tab_grid.addWidget(b3, 7, 1)
53 self.send_tab_grid.addWidget(b2, 7, 2)
55 self.send_tab_grid.addWidget(b2, 7, 1)
57 def is_available(self):
58 return self._is_available
60 def create_send_tab(self, grid):
61 self.send_tab_grid = grid
64 proc = zbar.Processor()
72 # User closed the preview window
75 for r in proc.results:
76 if str(r.type) != 'QRCODE':
80 def show_raw_qr(self):
81 r = unicode( self.gui.main_window.payto_e.text() )
84 # label or alias, with address in brackets
85 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
86 to_address = m.group(2) if m else r
88 if not is_valid(to_address):
89 QMessageBox.warning(self.gui.main_window, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
93 amount = self.gui.main_window.read_amount(unicode( self.gui.main_window.amount_e.text()))
95 QMessageBox.warning(self.gui.main_window, _('Error'), _('Invalid Amount'), _('OK'))
98 fee = self.gui.main_window.read_amount(unicode( self.gui.main_window.fee_e.text()))
100 QMessageBox.warning(self.gui.main_window, _('Error'), _('Invalid Fee'), _('OK'))
104 tx = self.gui.main_window.wallet.mktx( [(to_address, amount)], None, fee)
105 except BaseException, e:
106 self.gui.main_window.show_message(str(e))
109 if tx.requires_fee(self.gui.main_window.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
110 QMessageBox.warning(self.gui.main_window, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
121 except BaseException, e:
122 self.gui.main_window.show_message(str(e))
125 json_text = json.dumps(tx.as_dict()).replace(' ', '')
126 self.show_tx_qrcode(json_text, 'Unsigned Transaction')
127 except BaseException, e:
128 self.gui.main_window.show_message(str(e))
130 def show_tx_qrcode(self, data, title):
132 d = QDialog(self.gui.main_window)
134 d.setWindowTitle(title)
135 d.setMinimumSize(250, 525)
137 qrw = QRCodeWidget(data)
138 vbox.addWidget(qrw, 0)
143 filename = "qrcode.bmp"
144 electrum_gui.bmp.save_qrcode(qrw.qr, filename)
145 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
147 b = QPushButton(_("Save"))
149 b.clicked.connect(print_qr)
151 b = QPushButton(_("Close"))
153 b.clicked.connect(d.accept)
156 vbox.addLayout(hbox, 1)
160 def read_raw_qr(self):
161 qrcode = self.scan_qr()
163 tx_dict = self.gui.main_window.tx_dict_from_text(qrcode)
165 self.create_transaction_details_window(tx_dict)
168 def create_transaction_details_window(self, tx_dict):
169 tx = Transaction(tx_dict["hex"])
171 dialog = QDialog(self.gui.main_window)
172 dialog.setMinimumWidth(500)
173 dialog.setWindowTitle(_('Process Offline transaction'))
179 l.addWidget(QLabel(_("Transaction status:")), 3,0)
180 l.addWidget(QLabel(_("Actions")), 4,0)
182 if tx_dict["complete"] == False:
183 l.addWidget(QLabel(_("Unsigned")), 3,1)
184 if self.gui.main_window.wallet.seed :
185 b = QPushButton("Sign transaction")
186 input_info = json.loads(tx_dict["input_info"])
187 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
190 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
192 l.addWidget(QLabel(_("Signed")), 3,1)
193 b = QPushButton("Broadcast transaction")
194 b.clicked.connect(lambda: self.gui.main_window.send_raw_transaction(tx, dialog))
197 l.addWidget( self.gui.main_window.generate_transaction_information_widget(tx), 0,0,2,3)
198 closeButton = QPushButton(_("Close"))
199 closeButton.clicked.connect(lambda: dialog.done(0))
200 l.addWidget(closeButton, 4,2)
204 def do_protect(self, func, args):
205 if self.gui.main_window.wallet.use_encryption:
206 password = self.gui.main_window.password_dialog()
213 args = (self,) + args + (password,)
215 args = (self,password)
219 return lambda s, *args: s.do_protect(func, args)
222 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
224 self.gui.main_window.wallet.signrawtransaction(tx, input_info, [], password)
225 txtext = json.dumps(tx.as_dict()).replace(' ', '')
226 self.show_tx_qrcode(txtext, 'Signed Transaction')
227 except BaseException, e:
228 self.gui.main_window.show_message(str(e))
231 def fill_from_qr(self):
232 qrcode = parse_uri(self.scan_qr())
236 if 'address' in qrcode:
237 self.gui.main_window.payto_e.setText(qrcode['address'])
238 if 'amount' in qrcode:
239 self.gui.main_window.amount_e.setText(str(qrcode['amount']))
240 if 'label' in qrcode:
241 self.gui.main_window.message_e.setText(qrcode['label'])
242 if 'message' in qrcode:
243 self.gui.main_window.message_e.setText("%s (%s)" % (self.gui.main_window.message_e.text(), qrcode['message']))
250 # It's just an address (not BIP21)
251 return {'address': uri}
254 # Workaround for urlparse, it don't handle bitcoin: URI properly
255 uri = uri.replace(':', '://')
258 result = {'address': uri.netloc}
260 if uri.path.startswith('?'):
261 params = parse_qs(uri.path[1:])
263 params = parse_qs(uri.path)
265 for k,v in params.items():
266 if k in ('amount', 'label', 'message'):
275 if __name__ == '__main__':
278 assert(parse_uri('1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
279 {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
281 assert(parse_uri('bitcoin://1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
282 {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
284 assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
285 {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
287 assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN?amount=10') ==
288 {'amount': '10', 'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
290 assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN?amount=10&label=slush&message=Small%20tip%20to%20slush') ==
291 {'amount': '10', 'label': 'slush', 'message': 'Small tip to slush', 'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})