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.gui_classic.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 is_available(self):
43 return self._is_available
45 def create_send_tab(self, grid):
46 b = QPushButton(_("Scan QR code"))
47 b.clicked.connect(self.fill_from_qr)
48 grid.addWidget(b, 1, 5)
49 b2 = QPushButton(_("Scan TxQR"))
50 b2.clicked.connect(self.read_raw_qr)
52 if not self.gui.wallet.seed:
53 b3 = QPushButton(_("Show unsigned TxQR"))
54 b3.clicked.connect(self.show_raw_qr)
55 grid.addWidget(b3, 7, 1)
56 grid.addWidget(b2, 7, 2)
58 grid.addWidget(b2, 7, 1)
62 proc = zbar.Processor()
70 # User closed the preview window
73 for r in proc.results:
74 if str(r.type) != 'QRCODE':
78 def show_raw_qr(self):
79 r = unicode( self.gui.payto_e.text() )
82 # label or alias, with address in brackets
83 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
84 to_address = m.group(2) if m else r
86 if not is_valid(to_address):
87 QMessageBox.warning(self.gui, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
91 amount = self.gui.read_amount(unicode( self.gui.amount_e.text()))
93 QMessageBox.warning(self.gui, _('Error'), _('Invalid Amount'), _('OK'))
96 fee = self.gui.read_amount(unicode( self.gui.fee_e.text()))
98 QMessageBox.warning(self.gui, _('Error'), _('Invalid Fee'), _('OK'))
102 tx = self.gui.wallet.mktx( [(to_address, amount)], None, fee, account=self.gui.current_account)
103 except BaseException, e:
104 self.gui.show_message(str(e))
107 if tx.requires_fee(self.gui.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
108 QMessageBox.warning(self.gui, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
119 except BaseException, e:
120 self.gui.show_message(str(e))
123 json_text = json.dumps(tx.as_dict()).replace(' ', '')
124 self.show_tx_qrcode(json_text, 'Unsigned Transaction')
125 except BaseException, e:
126 self.gui.show_message(str(e))
128 def show_tx_qrcode(self, data, title):
130 d = QDialog(self.gui)
132 d.setWindowTitle(title)
133 d.setMinimumSize(250, 525)
135 qrw = QRCodeWidget(data)
136 vbox.addWidget(qrw, 0)
141 filename = "qrcode.bmp"
142 electrum_gui.bmp.save_qrcode(qrw.qr, filename)
143 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
145 b = QPushButton(_("Save"))
147 b.clicked.connect(print_qr)
149 b = QPushButton(_("Close"))
151 b.clicked.connect(d.accept)
154 vbox.addLayout(hbox, 1)
158 def read_raw_qr(self):
159 qrcode = self.scan_qr()
161 tx_dict = self.gui.tx_dict_from_text(qrcode)
163 self.create_transaction_details_window(tx_dict)
166 def create_transaction_details_window(self, tx_dict):
167 tx = Transaction(tx_dict["hex"])
169 dialog = QDialog(self.gui)
170 dialog.setMinimumWidth(500)
171 dialog.setWindowTitle(_('Process Offline transaction'))
177 l.addWidget(QLabel(_("Transaction status:")), 3,0)
178 l.addWidget(QLabel(_("Actions")), 4,0)
180 if tx_dict["complete"] == False:
181 l.addWidget(QLabel(_("Unsigned")), 3,1)
182 if self.gui.wallet.seed :
183 b = QPushButton("Sign transaction")
184 input_info = json.loads(tx_dict["input_info"])
185 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
188 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
190 l.addWidget(QLabel(_("Signed")), 3,1)
191 b = QPushButton("Broadcast transaction")
192 b.clicked.connect(lambda: self.gui.send_raw_transaction(tx, dialog))
195 l.addWidget( self.gui.generate_transaction_information_widget(tx), 0,0,2,3)
196 closeButton = QPushButton(_("Close"))
197 closeButton.clicked.connect(lambda: dialog.done(0))
198 l.addWidget(closeButton, 4,2)
202 def do_protect(self, func, args):
203 if self.gui.wallet.use_encryption:
204 password = self.gui.password_dialog()
211 args = (self,) + args + (password,)
213 args = (self,password)
217 return lambda s, *args: s.do_protect(func, args)
220 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
222 self.gui.wallet.signrawtransaction(tx, input_info, [], password)
223 txtext = json.dumps(tx.as_dict()).replace(' ', '')
224 self.show_tx_qrcode(txtext, 'Signed Transaction')
225 except BaseException, e:
226 self.gui.show_message(str(e))
229 def fill_from_qr(self):
230 qrcode = parse_uri(self.scan_qr())
234 if 'address' in qrcode:
235 self.gui.payto_e.setText(qrcode['address'])
236 if 'amount' in qrcode:
237 self.gui.amount_e.setText(str(qrcode['amount']))
238 if 'label' in qrcode:
239 self.gui.message_e.setText(qrcode['label'])
240 if 'message' in qrcode:
241 self.gui.message_e.setText("%s (%s)" % (self.gui.message_e.text(), qrcode['message']))
248 # It's just an address (not BIP21)
249 return {'address': uri}
252 # Workaround for urlparse, it don't handle bitcoin: URI properly
253 uri = uri.replace(':', '://')
256 result = {'address': uri.netloc}
258 if uri.path.startswith('?'):
259 params = parse_qs(uri.path[1:])
261 params = parse_qs(uri.path)
263 for k,v in params.items():
264 if k in ('amount', 'label', 'message'):
273 if __name__ == '__main__':
276 assert(parse_uri('1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
277 {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
279 assert(parse_uri('bitcoin://1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
280 {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
282 assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
283 {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
285 assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN?amount=10') ==
286 {'amount': '10', 'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
288 assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN?amount=10&label=slush&message=Small%20tip%20to%20slush') ==
289 {'amount': '10', 'label': 'slush', 'message': 'Small tip to slush', 'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})