Browse Source
faaf51eada Add BIP21 bitcoin payment URI support to sendpayment.py (Kristaps Kaupe)
Pull request description:
In future could and should be improved to add this also to Qt GUI, but didn't want to do that now, need to figure out proper UX there.
This is needed for #557.
Top commit has no ACKs.
Tree-SHA512: a3911f62c27b71796b27fb74be09f2cfb8e2f6f5b0ef0909ecafb16ef07b8023ea0f193f829bba2e580dc0880016d7c1e051e8d25f324e4a1178d112a69fa16c
master
7 changed files with 128 additions and 11 deletions
@ -0,0 +1,41 @@
|
||||
# https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki |
||||
# bitcoin:<address>[?amount=<amount>][?label=<label>][?message=<message>] |
||||
# We don't check validity of Bitcoin address here, as all the tools using |
||||
# this are expected to do address validation independently anyway. |
||||
|
||||
from jmbitcoin import amount_to_sat |
||||
from urllib.parse import parse_qs, urlparse |
||||
from url_decode import urldecode |
||||
import re |
||||
|
||||
|
||||
def is_bip21_uri(uri): |
||||
parsed = urlparse(uri) |
||||
return parsed.scheme == 'bitcoin' and parsed.path != '' |
||||
|
||||
|
||||
def is_bip21_amount_str(amount): |
||||
return re.compile("^[0-9]{1,8}(\.[0-9]{1,8})?$").match(amount) != None |
||||
|
||||
|
||||
def decode_bip21_uri(uri): |
||||
if not is_bip21_uri(uri): |
||||
raise ValueError("Not a valid BIP21 URI: " + uri) |
||||
result = {} |
||||
parsed = urlparse(uri) |
||||
result['address'] = parsed.path |
||||
params = parse_qs(parsed.query) |
||||
for key in params: |
||||
if key.startswith('req-'): |
||||
raise ValueError("Unknown required parameter " + key + |
||||
" in BIP21 URI.") |
||||
if key == 'amount': |
||||
amount_str = params['amount'][0] |
||||
if not is_bip21_amount_str(amount_str): |
||||
raise ValueError("Invalid BTC amount " + amount_str + |
||||
" vs. " + str(amount_to_sat(amount_str)) + " sat") |
||||
# Convert amount to sats, as used internally by JM |
||||
result['amount'] = amount_to_sat(amount_str + "btc") |
||||
else: |
||||
result[key] = urldecode(params[key][0]) |
||||
return result |
||||
@ -0,0 +1,58 @@
|
||||
import jmbitcoin as btc |
||||
import pytest |
||||
|
||||
|
||||
def test_bip21(): |
||||
|
||||
# These should raise exception because of not being valid BIP21 URI's |
||||
with pytest.raises(ValueError): |
||||
btc.decode_bip21_uri('') |
||||
btc.decode_bip21_uri('nfdjksnfjkdsnfjkds') |
||||
btc.decode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||
btc.decode_bip21_uri( |
||||
'175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=20.3') |
||||
btc.decode_bip21_uri('bitcoin:') |
||||
btc.decode_bip21_uri('bitcoin:?amount=20.3') |
||||
btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=') |
||||
btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=XYZ') |
||||
btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100\'000') |
||||
btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100,000') |
||||
btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100000000') |
||||
|
||||
assert(btc.decode_bip21_uri('bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W' |
||||
)['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||
|
||||
parsed = btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?label=Luke-Jr') |
||||
assert(parsed['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||
assert(parsed['label'] == 'Luke-Jr') |
||||
|
||||
parsed = btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=20.3&label=Luke-Jr') |
||||
assert(parsed['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||
assert(parsed['amount'] == 2030000000) |
||||
assert(parsed['label'] == 'Luke-Jr') |
||||
|
||||
parsed = btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz') |
||||
assert(parsed['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||
assert(parsed['amount'] == 5000000000) |
||||
assert(parsed['label'] == 'Luke-Jr') |
||||
assert(parsed['message'] == 'Donation for project xyz') |
||||
|
||||
# This should raise exception because of unknown req-* parameters |
||||
with pytest.raises(ValueError): |
||||
btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-somethingyoudontunderstand=50&req-somethingelseyoudontget=999') |
||||
|
||||
parsed = btc.decode_bip21_uri( |
||||
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?somethingyoudontunderstand=50&somethingelseyoudontget=999') |
||||
assert(parsed['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||
assert(parsed['somethingyoudontunderstand'] == '50') |
||||
assert(parsed['somethingelseyoudontget'] == '999') |
||||
|
||||
Loading…
Reference in new issue