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