improve requests
This commit is contained in:
parent
fbc68d94d6
commit
48e53498db
|
@ -41,6 +41,7 @@ from electrum import mnemonic
|
||||||
from electrum import util, bitcoin, commands, Wallet
|
from electrum import util, bitcoin, commands, Wallet
|
||||||
from electrum import SimpleConfig, Wallet, WalletStorage
|
from electrum import SimpleConfig, Wallet, WalletStorage
|
||||||
from electrum import Imported_Wallet
|
from electrum import Imported_Wallet
|
||||||
|
from electrum import paymentrequest
|
||||||
|
|
||||||
from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
|
from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
|
||||||
from network_dialog import NetworkDialog
|
from network_dialog import NetworkDialog
|
||||||
|
@ -721,8 +722,8 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
def export_payment_request(self, addr):
|
def export_payment_request(self, addr):
|
||||||
r = self.wallet.get_payment_request(addr)
|
r = self.wallet.get_payment_request(addr)
|
||||||
pr = self.wallet.make_bip70_request(self.config, r)
|
pr = paymentrequest.make_request(self.config, r)
|
||||||
name = 'request.bip70'
|
name = r['key'] + '.bip70'
|
||||||
fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70")
|
fileName = self.getSaveFileName(_("Select where to save your payment request"), name, "*.bip70")
|
||||||
if fileName:
|
if fileName:
|
||||||
with open(fileName, "wb+") as f:
|
with open(fileName, "wb+") as f:
|
||||||
|
@ -804,17 +805,18 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
# clear the list and fill it again
|
# clear the list and fill it again
|
||||||
self.receive_list.clear()
|
self.receive_list.clear()
|
||||||
for address, req in self.wallet.receive_requests.viewitems():
|
for req in self.wallet.get_sorted_requests():
|
||||||
timestamp, amount = req['time'], req['amount']
|
address = req['address']
|
||||||
expiration = req.get('expiration', None)
|
|
||||||
message = self.wallet.labels.get(address, '')
|
|
||||||
# only show requests for the current account
|
|
||||||
if address not in domain:
|
if address not in domain:
|
||||||
continue
|
continue
|
||||||
|
timestamp = req['time']
|
||||||
|
amount = req.get('amount')
|
||||||
|
expiration = req.get('expiration', None)
|
||||||
|
message = req.get('reason', '')
|
||||||
date = format_time(timestamp)
|
date = format_time(timestamp)
|
||||||
|
status = req.get('status')
|
||||||
account = self.wallet.get_account_name(self.wallet.get_account_from_address(address))
|
account = self.wallet.get_account_name(self.wallet.get_account_from_address(address))
|
||||||
amount_str = self.format_amount(amount) if amount else ""
|
amount_str = self.format_amount(amount) if amount else ""
|
||||||
status = self.wallet.get_request_status(address, amount, timestamp, expiration)
|
|
||||||
item = QTreeWidgetItem([date, account, address, message, amount_str, pr_tooltips.get(status,'')])
|
item = QTreeWidgetItem([date, account, address, message, amount_str, pr_tooltips.get(status,'')])
|
||||||
if status is not PR_UNKNOWN:
|
if status is not PR_UNKNOWN:
|
||||||
item.setIcon(5, QIcon(pr_icons.get(status)))
|
item.setIcon(5, QIcon(pr_icons.get(status)))
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
@ -31,7 +32,7 @@ from util import print_msg, format_satoshis, print_stderr
|
||||||
import bitcoin
|
import bitcoin
|
||||||
from bitcoin import is_address, hash_160_to_bc_address, hash_160, COIN
|
from bitcoin import is_address, hash_160_to_bc_address, hash_160, COIN
|
||||||
from transaction import Transaction
|
from transaction import Transaction
|
||||||
|
import paymentrequest
|
||||||
|
|
||||||
known_commands = {}
|
known_commands = {}
|
||||||
|
|
||||||
|
@ -516,7 +517,7 @@ class Commands:
|
||||||
"""Decrypt a message encrypted with a public key."""
|
"""Decrypt a message encrypted with a public key."""
|
||||||
return self.wallet.decrypt_message(pubkey, encrypted, self.password)
|
return self.wallet.decrypt_message(pubkey, encrypted, self.password)
|
||||||
|
|
||||||
def _format_request(self, v, show_status=False):
|
def _format_request(self, v):
|
||||||
from paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
|
from paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
|
||||||
pr_str = {
|
pr_str = {
|
||||||
PR_UNKNOWN: 'Unknown',
|
PR_UNKNOWN: 'Unknown',
|
||||||
|
@ -524,42 +525,66 @@ class Commands:
|
||||||
PR_PAID: 'Paid',
|
PR_PAID: 'Paid',
|
||||||
PR_EXPIRED: 'Expired',
|
PR_EXPIRED: 'Expired',
|
||||||
}
|
}
|
||||||
|
key = v['key']
|
||||||
addr = v.get('address')
|
addr = v.get('address')
|
||||||
amount = v.get('amount')
|
amount = v.get('amount')
|
||||||
timestamp = v.get('time')
|
timestamp = v.get('time')
|
||||||
expiration = v.get('expiration')
|
expiration = v.get('expiration')
|
||||||
out = {
|
out = {
|
||||||
|
'key': key,
|
||||||
'address': addr,
|
'address': addr,
|
||||||
'amount': format_satoshis(amount),
|
'amount': format_satoshis(amount),
|
||||||
'time': timestamp,
|
'timestamp': timestamp,
|
||||||
'reason': self.wallet.get_label(addr)[0],
|
'reason': v.get('reason'),
|
||||||
'expiration': expiration,
|
'expiration': expiration,
|
||||||
|
'URI':'bitcoin:' + addr + '?amount=' + format_satoshis(amount),
|
||||||
|
'status': pr_str[v.get('status', PR_UNKNOWN)]
|
||||||
}
|
}
|
||||||
if v.get('path'):
|
# check if bip70 file exists
|
||||||
url = 'file://' + v.get('path')
|
rdir = self.config.get('requests_dir')
|
||||||
|
if rdir:
|
||||||
|
path = os.path.join(rdir, key + '.bip70')
|
||||||
|
if os.path.exists(path):
|
||||||
|
out['path'] = path
|
||||||
|
url = 'file://' + path
|
||||||
r = self.config.get('url_rewrite')
|
r = self.config.get('url_rewrite')
|
||||||
if r:
|
if r:
|
||||||
a, b = r
|
a, b = r
|
||||||
url = url.replace(a, b)
|
url = url.replace(a, b)
|
||||||
URI = 'bitcoin:?r=' + url
|
out['request_url'] = url
|
||||||
out['URI'] = URI
|
out['URI'] += '&r=' + url
|
||||||
if show_status:
|
|
||||||
status = self.wallet.get_request_status(addr, amount, timestamp, expiration)
|
|
||||||
out['status'] = pr_str[status]
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@command('w')
|
@command('wn')
|
||||||
def listrequests(self, status=False):
|
def getrequest(self, key):
|
||||||
"""List the payment requests you made, and their status"""
|
"""Return a payment request"""
|
||||||
return map(lambda x: self._format_request(x, status), self.wallet.receive_requests.values())
|
r = self.wallet.get_payment_request(key)
|
||||||
|
if not r:
|
||||||
|
raise BaseException("Request not found")
|
||||||
|
return self._format_request(r)
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
def addrequest(self, requested_amount, reason='', expiration=60*60):
|
def ackrequest(self, serialized):
|
||||||
"""Create a payment request.
|
"""<Not implemented>"""
|
||||||
"""
|
pass
|
||||||
|
|
||||||
|
@command('w')
|
||||||
|
def listrequests(self):
|
||||||
|
"""List the payment requests you made, and their status"""
|
||||||
|
return map(self._format_request, self.wallet.get_sorted_requests())
|
||||||
|
|
||||||
|
@command('w')
|
||||||
|
def addrequest(self, requested_amount, reason='', expiration=None):
|
||||||
|
"""Create a payment request."""
|
||||||
amount = int(Decimal(requested_amount)*COIN)
|
amount = int(Decimal(requested_amount)*COIN)
|
||||||
key = self.wallet.add_payment_request(self.config, amount, reason, expiration)
|
key = self.wallet.add_payment_request(amount, reason, expiration)
|
||||||
return self._format_request(self.wallet.get_payment_request(key)) if key else False
|
if key is None:
|
||||||
|
return
|
||||||
|
# create file
|
||||||
|
req = self.wallet.get_payment_request(key)
|
||||||
|
paymentrequest.publish_request(self.config, key, req)
|
||||||
|
return self._format_request(req)
|
||||||
|
|
||||||
@command('w')
|
@command('w')
|
||||||
def rmrequest(self, address):
|
def rmrequest(self, address):
|
||||||
|
@ -659,6 +684,8 @@ def set_default_subparser(self, name, args=None):
|
||||||
|
|
||||||
argparse.ArgumentParser.set_default_subparser = set_default_subparser
|
argparse.ArgumentParser.set_default_subparser = set_default_subparser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_network_options(parser):
|
def add_network_options(parser):
|
||||||
parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
|
parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
|
||||||
parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)")
|
parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)")
|
||||||
|
|
|
@ -262,13 +262,12 @@ class PaymentRequest:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def make_payment_request(outputs, memo, time, expires, key_path, cert_path):
|
def make_payment_request(outputs, memo, time, expires, key_path, cert_path):
|
||||||
pd = pb2.PaymentDetails()
|
pd = pb2.PaymentDetails()
|
||||||
for script, amount in outputs:
|
for script, amount in outputs:
|
||||||
pd.outputs.add(amount=amount, script=script)
|
pd.outputs.add(amount=amount, script=script)
|
||||||
pd.time = time
|
pd.time = time
|
||||||
pd.expires = expires
|
pd.expires = expires if expires else 0
|
||||||
pd.memo = memo
|
pd.memo = memo
|
||||||
pr = pb2.PaymentRequest()
|
pr = pb2.PaymentRequest()
|
||||||
pr.serialized_payment_details = pd.SerializeToString()
|
pr.serialized_payment_details = pd.SerializeToString()
|
||||||
|
@ -294,6 +293,37 @@ def make_payment_request(outputs, memo, time, expires, key_path, cert_path):
|
||||||
return pr.SerializeToString()
|
return pr.SerializeToString()
|
||||||
|
|
||||||
|
|
||||||
|
def make_request(config, req):
|
||||||
|
from transaction import Transaction
|
||||||
|
addr = req['address']
|
||||||
|
time = req['time']
|
||||||
|
amount = req['amount']
|
||||||
|
expiration = req['expiration']
|
||||||
|
message = req['reason']
|
||||||
|
script = Transaction.pay_script('address', addr).decode('hex')
|
||||||
|
outputs = [(script, amount)]
|
||||||
|
key_path = config.get('ssl_privkey')
|
||||||
|
cert_path = config.get('ssl_chain')
|
||||||
|
return make_payment_request(outputs, message, time, time + expiration if expiration else None, key_path, cert_path)
|
||||||
|
|
||||||
|
|
||||||
|
def publish_request(config, key, req):
|
||||||
|
import shutil, os
|
||||||
|
rdir = config.get('requests_dir')
|
||||||
|
if not rdir:
|
||||||
|
return
|
||||||
|
if not os.path.exists(rdir):
|
||||||
|
os.mkdir(rdir)
|
||||||
|
index = os.path.join(rdir, 'index.html')
|
||||||
|
if not os.path.exists(index):
|
||||||
|
src = os.path.join(os.path.dirname(__file__), 'payrequest.html')
|
||||||
|
shutil.copy(src, index)
|
||||||
|
pr = make_request(config, req)
|
||||||
|
path = os.path.join(rdir, key + '.bip70')
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(pr)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class InvoiceStore(object):
|
class InvoiceStore(object):
|
||||||
|
|
|
@ -1231,25 +1231,22 @@ class Abstract_Wallet(object):
|
||||||
if not self.history.get(addr) and addr not in self.receive_requests.keys():
|
if not self.history.get(addr) and addr not in self.receive_requests.keys():
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
|
|
||||||
def make_bip70_request(self, config, req):
|
|
||||||
from paymentrequest import make_payment_request
|
|
||||||
addr = req['address']
|
|
||||||
time = req['time']
|
|
||||||
amount = req['amount']
|
|
||||||
expiration = req['expiration']
|
|
||||||
message = self.labels.get(addr, '')
|
|
||||||
script = Transaction.pay_script('address', addr).decode('hex')
|
|
||||||
outputs = [(script, amount)]
|
|
||||||
key_path = config.get('ssl_privkey')
|
|
||||||
cert_path = config.get('ssl_chain')
|
|
||||||
return make_payment_request(outputs, message, time, time + expiration, key_path, cert_path)
|
|
||||||
|
|
||||||
def get_payment_request(self, key):
|
def get_payment_request(self, key):
|
||||||
return self.receive_requests.get(key)
|
r = self.receive_requests.get(key)
|
||||||
|
if not r:
|
||||||
|
return
|
||||||
|
r['reason'] = self.labels.get(key, '')
|
||||||
|
r['status'] = self.get_request_status(key)
|
||||||
|
r['key'] = key
|
||||||
|
return r
|
||||||
|
|
||||||
def get_request_status(self, address, amount, timestamp, expiration):
|
def get_request_status(self, key):
|
||||||
from paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
|
from paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
|
||||||
|
r = self.receive_requests[key]
|
||||||
|
address = r['address']
|
||||||
|
amount = r.get('amount')
|
||||||
|
timestamp = r.get('time')
|
||||||
|
expiration = r.get('expiration')
|
||||||
if amount:
|
if amount:
|
||||||
paid = amount <= self.get_addr_received(address)
|
paid = amount <= self.get_addr_received(address)
|
||||||
status = PR_PAID if paid else PR_UNPAID
|
status = PR_PAID if paid else PR_UNPAID
|
||||||
|
@ -1259,27 +1256,18 @@ class Abstract_Wallet(object):
|
||||||
status = PR_UNKNOWN
|
status = PR_UNKNOWN
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def save_payment_request(self, config, addr, amount, message, expiration):
|
def save_payment_request(self, addr, amount, message, expiration):
|
||||||
#if addr in self.receive_requests:
|
|
||||||
# self.receive_requests[addr]['amount'] = amount
|
|
||||||
self.set_label(addr, message)
|
self.set_label(addr, message)
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
r = {'time':now, 'amount':amount, 'expiration':expiration, 'address':addr}
|
r = {'time':now, 'amount':amount, 'expiration':expiration, 'address':addr}
|
||||||
rdir = config.get('requests_dir')
|
|
||||||
if rdir:
|
|
||||||
pr = self.make_bip70_request(config, r)
|
|
||||||
path = os.path.join(rdir, addr + '.bip70')
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
f.write(pr)
|
|
||||||
r['path'] = path
|
|
||||||
self.receive_requests[addr] = r
|
self.receive_requests[addr] = r
|
||||||
self.storage.put('receive_requests2', self.receive_requests)
|
self.storage.put('receive_requests2', self.receive_requests)
|
||||||
|
|
||||||
def add_payment_request(self, config, amount, message, expiration):
|
def add_payment_request(self, amount, message, expiration):
|
||||||
addr = self.get_unused_address(None)
|
addr = self.get_unused_address(None)
|
||||||
if addr is None:
|
if addr is None:
|
||||||
return False
|
return
|
||||||
self.save_payment_request(config, addr, amount, message, expiration)
|
self.save_payment_request(addr, amount, message, expiration)
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
def remove_payment_request(self, addr):
|
def remove_payment_request(self, addr):
|
||||||
|
@ -1289,6 +1277,10 @@ class Abstract_Wallet(object):
|
||||||
self.storage.put('receive_requests2', self.receive_requests)
|
self.storage.put('receive_requests2', self.receive_requests)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_sorted_requests(self):
|
||||||
|
return sorted(map(self.get_payment_request, self.receive_requests.keys()), key=itemgetter('time'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Imported_Wallet(Abstract_Wallet):
|
class Imported_Wallet(Abstract_Wallet):
|
||||||
wallet_type = 'imported'
|
wallet_type = 'imported'
|
||||||
|
|
Loading…
Reference in New Issue