generate and parse bip 21 qr codes
This commit is contained in:
@@ -40,6 +40,21 @@ Pane {
|
|||||||
Layout.preferredWidth: parent.width /2
|
Layout.preferredWidth: parent.width /2
|
||||||
placeholderText: qsTr('Amount')
|
placeholderText: qsTr('Amount')
|
||||||
inputMethodHints: Qt.ImhPreferNumbers
|
inputMethodHints: Qt.ImhPreferNumbers
|
||||||
|
|
||||||
|
property string textAsSats
|
||||||
|
onTextChanged: {
|
||||||
|
textAsSats = Config.unitsToSats(amount.text)
|
||||||
|
if (amountFiat.activeFocus)
|
||||||
|
return
|
||||||
|
amountFiat.text = Daemon.fx.fiatValue(amount.textAsSats)
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Config
|
||||||
|
function onBaseUnitChanged() {
|
||||||
|
amount.text = amount.textAsSats != 0 ? Config.satsToUnits(amount.textAsSats) : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -58,6 +73,10 @@ Pane {
|
|||||||
Layout.preferredWidth: parent.width /2
|
Layout.preferredWidth: parent.width /2
|
||||||
placeholderText: qsTr('Amount')
|
placeholderText: qsTr('Amount')
|
||||||
inputMethodHints: Qt.ImhDigitsOnly
|
inputMethodHints: Qt.ImhDigitsOnly
|
||||||
|
onTextChanged: {
|
||||||
|
if (amountFiat.activeFocus)
|
||||||
|
amount.text = Daemon.fx.satoshiValue(amountFiat.text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -216,7 +235,7 @@ Pane {
|
|||||||
text: qsTr('Timestamp: ')
|
text: qsTr('Timestamp: ')
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: model.timestamp
|
text: model.date
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -301,32 +320,11 @@ Pane {
|
|||||||
}
|
}
|
||||||
dialog.open()
|
dialog.open()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Daemon.currentWallet
|
|
||||||
function onRequestStatusChanged(key, status) {
|
function onRequestStatusChanged(key, status) {
|
||||||
Daemon.currentWallet.requestModel.updateRequest(key, status)
|
Daemon.currentWallet.requestModel.updateRequest(key, status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: amount
|
|
||||||
function onTextChanged() {
|
|
||||||
if (amountFiat.activeFocus)
|
|
||||||
return
|
|
||||||
var a = Config.unitsToSats(amount.text)
|
|
||||||
amountFiat.text = Daemon.fx.fiatValue(a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
target: amountFiat
|
|
||||||
function onTextChanged() {
|
|
||||||
if (amountFiat.activeFocus) {
|
|
||||||
amount.text = Daemon.fx.satoshiValue(amountFiat.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Daemon.fx
|
target: Daemon.fx
|
||||||
function onQuotesUpdated() {
|
function onQuotesUpdated() {
|
||||||
|
|||||||
@@ -56,13 +56,12 @@ Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
|
id: qr
|
||||||
Layout.columnSpan: 3
|
Layout.columnSpan: 3
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: constants.paddingSmall
|
Layout.topMargin: constants.paddingSmall
|
||||||
Layout.bottomMargin: constants.paddingSmall
|
Layout.bottomMargin: constants.paddingSmall
|
||||||
|
|
||||||
source: 'image://qrgen/' + modelItem.address
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
property int size: 57 // should be qr pixel multiple
|
property int size: 57 // should be qr pixel multiple
|
||||||
color: 'white'
|
color: 'white'
|
||||||
@@ -131,7 +130,7 @@ Dialog {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
visible: modelItem.amount > 0
|
visible: modelItem.amount > 0
|
||||||
text: Config.formatSats(modelItem.amount, false)
|
text: Config.formatSats(modelItem.amount)
|
||||||
font.family: FixedFont
|
font.family: FixedFont
|
||||||
font.pixelSize: constants.fontSizeLarge
|
font.pixelSize: constants.fontSizeLarge
|
||||||
}
|
}
|
||||||
@@ -181,4 +180,13 @@ Dialog {
|
|||||||
modelItem = Daemon.currentWallet.get_request(key)
|
modelItem = Daemon.currentWallet.get_request(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
var bip21uri = bitcoin.create_uri(modelItem.address, modelItem.amount, modelItem.message, modelItem.timestamp, modelItem.exp)
|
||||||
|
qr.source = 'image://qrgen/' + bip21uri
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitcoin {
|
||||||
|
id: bitcoin
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import QtQuick 2.6
|
import QtQuick 2.6
|
||||||
import QtQuick.Controls 2.0
|
import QtQuick.Controls 2.0
|
||||||
|
|
||||||
|
import org.electrum 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: scanPage
|
id: scanPage
|
||||||
property string title: qsTr('Scan')
|
property string title: qsTr('Scan')
|
||||||
@@ -8,6 +10,8 @@ Item {
|
|||||||
property bool toolbar: false
|
property bool toolbar: false
|
||||||
|
|
||||||
property string scanData
|
property string scanData
|
||||||
|
property var invoiceData: undefined
|
||||||
|
property string error
|
||||||
|
|
||||||
signal found
|
signal found
|
||||||
|
|
||||||
@@ -18,6 +22,16 @@ Item {
|
|||||||
|
|
||||||
onFound: {
|
onFound: {
|
||||||
scanPage.scanData = scanData
|
scanPage.scanData = scanData
|
||||||
|
var invoice = bitcoin.parse_uri(scanData)
|
||||||
|
if (invoice['error']) {
|
||||||
|
error = invoice['error']
|
||||||
|
console.log(error)
|
||||||
|
app.stack.pop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceData = invoice
|
||||||
|
console.log(invoiceData['address'])
|
||||||
scanPage.found()
|
scanPage.found()
|
||||||
app.stack.pop()
|
app.stack.pop()
|
||||||
}
|
}
|
||||||
@@ -31,4 +45,7 @@ Item {
|
|||||||
onClicked: app.stack.pop()
|
onClicked: app.stack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bitcoin {
|
||||||
|
id: bitcoin
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,20 @@ Pane {
|
|||||||
placeholderText: qsTr('Amount')
|
placeholderText: qsTr('Amount')
|
||||||
Layout.preferredWidth: parent.width /2
|
Layout.preferredWidth: parent.width /2
|
||||||
inputMethodHints: Qt.ImhPreferNumbers
|
inputMethodHints: Qt.ImhPreferNumbers
|
||||||
|
property string textAsSats
|
||||||
|
onTextChanged: {
|
||||||
|
textAsSats = Config.unitsToSats(amount.text)
|
||||||
|
if (amountFiat.activeFocus)
|
||||||
|
return
|
||||||
|
amountFiat.text = Daemon.fx.fiatValue(amount.textAsSats)
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Config
|
||||||
|
function onBaseUnitChanged() {
|
||||||
|
amount.text = amount.textAsSats != 0 ? Config.satsToUnits(amount.textAsSats) : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -67,6 +81,10 @@ Pane {
|
|||||||
Layout.preferredWidth: parent.width /2
|
Layout.preferredWidth: parent.width /2
|
||||||
placeholderText: qsTr('Amount')
|
placeholderText: qsTr('Amount')
|
||||||
inputMethodHints: Qt.ImhPreferNumbers
|
inputMethodHints: Qt.ImhPreferNumbers
|
||||||
|
onTextChanged: {
|
||||||
|
if (amountFiat.activeFocus)
|
||||||
|
amount.text = Daemon.fx.satoshiValue(amountFiat.text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -113,31 +131,15 @@ Pane {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
var page = app.stack.push(Qt.resolvedUrl('Scan.qml'))
|
var page = app.stack.push(Qt.resolvedUrl('Scan.qml'))
|
||||||
page.onFound.connect(function() {
|
page.onFound.connect(function() {
|
||||||
console.log('got ' + page.scanData)
|
console.log('got ' + page.invoiceData)
|
||||||
address.text = page.scanData
|
address.text = page.invoiceData['address']
|
||||||
|
amount.text = Config.formatSats(page.invoiceData['amount'])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: amount
|
|
||||||
function onTextChanged() {
|
|
||||||
if (amountFiat.activeFocus)
|
|
||||||
return
|
|
||||||
var a = Config.unitsToSats(amount.text)
|
|
||||||
amountFiat.text = Daemon.fx.fiatValue(a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
target: amountFiat
|
|
||||||
function onTextChanged() {
|
|
||||||
if (amountFiat.activeFocus) {
|
|
||||||
amount.text = Daemon.fx.satoshiValue(amountFiat.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Daemon.fx
|
target: Daemon.fx
|
||||||
function onQuotesUpdated() {
|
function onQuotesUpdated() {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||||
|
|
||||||
@@ -7,6 +8,7 @@ from electrum.keystore import bip39_is_checksum_valid
|
|||||||
from electrum.bip32 import is_bip32_derivation
|
from electrum.bip32 import is_bip32_derivation
|
||||||
from electrum.slip39 import decode_mnemonic, Slip39Error
|
from electrum.slip39 import decode_mnemonic, Slip39Error
|
||||||
from electrum import mnemonic
|
from electrum import mnemonic
|
||||||
|
from electrum.util import parse_URI, create_bip21_uri, InvalidBitcoinURI
|
||||||
|
|
||||||
class QEBitcoin(QObject):
|
class QEBitcoin(QObject):
|
||||||
def __init__(self, config, parent=None):
|
def __init__(self, config, parent=None):
|
||||||
@@ -111,3 +113,19 @@ class QEBitcoin(QObject):
|
|||||||
@pyqtSlot(str, result=bool)
|
@pyqtSlot(str, result=bool)
|
||||||
def verify_derivation_path(self, path):
|
def verify_derivation_path(self, path):
|
||||||
return is_bip32_derivation(path)
|
return is_bip32_derivation(path)
|
||||||
|
|
||||||
|
@pyqtSlot(str, result='QVariantMap')
|
||||||
|
def parse_uri(self, uri: str) -> dict:
|
||||||
|
try:
|
||||||
|
return parse_URI(uri)
|
||||||
|
except InvalidBitcoinURI as e:
|
||||||
|
return { 'error': str(e) }
|
||||||
|
|
||||||
|
@pyqtSlot(str, 'qint64', str, int, int, result=str)
|
||||||
|
def create_uri(self, address, satoshis, message, timestamp, expiry):
|
||||||
|
extra_params = {}
|
||||||
|
if expiry:
|
||||||
|
extra_params['time'] = str(timestamp)
|
||||||
|
extra_params['exp'] = str(expiry)
|
||||||
|
|
||||||
|
return create_bip21_uri(address, satoshis, message, extra_query_params=extra_params)
|
||||||
|
|||||||
@@ -100,3 +100,7 @@ class QEConfig(QObject):
|
|||||||
#amount = Decimal(max_prec_amount) / Decimal(pow(10, self.max_precision()-self.decimal_point()))
|
#amount = Decimal(max_prec_amount) / Decimal(pow(10, self.max_precision()-self.decimal_point()))
|
||||||
#return int(amount) #Decimal(amount) if not self.is_int else int(amount)
|
#return int(amount) #Decimal(amount) if not self.is_int else int(amount)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@pyqtSlot('quint64', result=float)
|
||||||
|
def satsToUnits(self, satoshis):
|
||||||
|
return satoshis / pow(10,self.config.decimal_point)
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ from PyQt5.QtQuick import QQuickImageProvider
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import qrcode
|
import qrcode
|
||||||
#from qrcode.image.styledpil import StyledPilImage
|
|
||||||
#from qrcode.image.styles.moduledrawers import *
|
|
||||||
from PIL import Image, ImageQt
|
from PIL import Image, ImageQt
|
||||||
|
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
@@ -126,10 +125,10 @@ class QEQRImageProvider(QQuickImageProvider):
|
|||||||
|
|
||||||
def requestImage(self, qstr, size):
|
def requestImage(self, qstr, size):
|
||||||
self._logger.debug('QR requested for %s' % qstr)
|
self._logger.debug('QR requested for %s' % qstr)
|
||||||
qr = qrcode.QRCode(version=1, box_size=8, border=2)
|
qr = qrcode.QRCode(version=1, box_size=6, border=2)
|
||||||
qr.add_data(qstr)
|
qr.add_data(qstr)
|
||||||
qr.make(fit=True)
|
qr.make(fit=True)
|
||||||
|
|
||||||
pimg = qr.make_image(fill_color='black', back_color='white') #image_factory=StyledPilImage, module_drawer=CircleModuleDrawer())
|
pimg = qr.make_image(fill_color='black', back_color='white')
|
||||||
self.qimg = ImageQt.ImageQt(pimg)
|
self.qimg = ImageQt.ImageQt(pimg)
|
||||||
return self.qimg, self.qimg.size()
|
return self.qimg, self.qimg.size()
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class QERequestListModel(QAbstractListModel):
|
|||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
# define listmodel rolemap
|
# define listmodel rolemap
|
||||||
_ROLE_NAMES=('key','type','timestamp','message','amount','status','address')
|
_ROLE_NAMES=('key','type','timestamp','date','message','amount','status','address','exp')
|
||||||
_ROLE_KEYS = range(Qt.UserRole + 1, Qt.UserRole + 1 + len(_ROLE_NAMES))
|
_ROLE_KEYS = range(Qt.UserRole + 1, Qt.UserRole + 1 + len(_ROLE_NAMES))
|
||||||
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
|
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
|
||||||
_ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS))
|
_ROLE_RMAP = dict(zip(_ROLE_NAMES, _ROLE_KEYS))
|
||||||
@@ -46,10 +46,11 @@ class QERequestListModel(QAbstractListModel):
|
|||||||
status = self.wallet.get_request_status(key)
|
status = self.wallet.get_request_status(key)
|
||||||
item['status'] = req.get_status_str(status)
|
item['status'] = req.get_status_str(status)
|
||||||
item['type'] = req.type # 0=onchain, 2=LN
|
item['type'] = req.type # 0=onchain, 2=LN
|
||||||
timestamp = req.time
|
item['timestamp'] = req.time
|
||||||
item['timestamp'] = format_time(timestamp)
|
item['date'] = format_time(item['timestamp'])
|
||||||
item['amount'] = req.get_amount_sat()
|
item['amount'] = req.get_amount_sat()
|
||||||
item['message'] = req.message
|
item['message'] = req.message
|
||||||
|
item['exp'] = req.exp
|
||||||
if req.type == 0: # OnchainInvoice
|
if req.type == 0: # OnchainInvoice
|
||||||
item['key'] = item['address'] = req.get_address()
|
item['key'] = item['address'] = req.get_address()
|
||||||
elif req.type == 2: # LNInvoice
|
elif req.type == 2: # LNInvoice
|
||||||
|
|||||||
Reference in New Issue
Block a user