produce .gif BBQr with --bbqr flag

This commit is contained in:
scgbckbone 2025-12-03 15:48:16 +01:00 committed by doc-hex
parent 614bef5932
commit 2d42c86e70
4 changed files with 50 additions and 6 deletions

View File

@ -40,6 +40,7 @@ Options:
num-outs
-f, --fee INTEGER Miner's fee in Satoshis
-2, --psbt2 Make PSBTv2
-b, --bbqr Output BBQr OUTPUT.gif in CWD
-s, --segwit [SS] Make inputs be segwit style
-w, --wrapped [SS] Make inputs be wrapped segwit style
(requires --segwit flag)

View File

@ -7,12 +7,15 @@
# That will create the command "psbt_faker" in your path... or just use "./main.py ..." here
#
#
import click
import click, io, pyqrcode, os
from binascii import b2a_hex as _b2a_hex
from base64 import b64encode
from decimal import Decimal
from .txn import fake_ms_txn, fake_txn, ADDR_STYLES
from .multisig import from_simple_text
from bbqr import split_qrs
from PIL import Image, ImageDraw, ImageChops
b2a_hex = lambda a: str(_b2a_hex(a), 'ascii')
#xfp2hex = lambda a: b2a_hex(a[::-1]).upper()
@ -36,6 +39,7 @@ def repeat_until_length(values, target_length):
@click.option('--num-change', '-c', help="Number of change outputs (default 1) from num-outs", default=1)
@click.option('--fee', '-f', help="Miner's fee in Satoshis", default=1000)
@click.option('--psbt2', '-2', help="Make PSBTv2", is_flag=True, default=False)
@click.option('--bbqr', '-b', help="Output BBQr OUTPUT.gif in CWD", is_flag=True, default=False)
@click.option('--segwit', '-s', help="[SS] Make inputs be segwit style", is_flag=True, default=False)
@click.option('--wrapped', '-w', help="[SS] Make inputs be wrapped segwit style (requires --segwit flag)", is_flag=True, default=False)
@click.option('--styles', '-a', help="Output address style (multiple ok). If multisig only applies to non-change addresses.", multiple=True, default=None, type=click.Choice(ADDR_STYLES))
@ -48,7 +52,7 @@ def repeat_until_length(values, target_length):
@click.option('--input-amount', '-n', help="Size of each input in sats (default 100k sats each input)", default=(100000,), multiple=True, type=int)
@click.option('--incl-xpubs', '-I', help="[MS] Include XPUBs in PSBT global section", is_flag=True, default=False)
def main(num_ins, sequence, num_change, num_outs, output_amount, out_psbt, testnet, xpub, segwit, fee, styles, base64,
partial, zero_xfp, multisig, locktime, input_amount, psbt2, incl_xpubs, wrapped):
partial, zero_xfp, multisig, locktime, input_amount, psbt2, incl_xpubs, wrapped, bbqr):
'''Construct a valid PSBT which spends non-existant BTC to random addresses!'''
if locktime == "current":
@ -99,8 +103,8 @@ def main(num_ins, sequence, num_change, num_outs, output_amount, out_psbt, testn
change_outputs=change_outputs,
psbt_v2=psbt2, sequences=sequence)
out_psbt.write(psbt if not base64 else b64encode(psbt))
res = b64encode(psbt) if base64 else psbt
out_psbt.write(res)
print(f"\nFake PSBT would send {(sum(input_amount)/Decimal(1E8))} BTC to: ")
print('\n'.join(" %.8f => %s %s" % (Decimal(amt)/Decimal(1E8),dest, ' (change back)' if chg else '')
@ -110,6 +114,37 @@ def main(num_ins, sequence, num_change, num_outs, output_amount, out_psbt, testn
print("\nPSBT to be signed: " + out_psbt.name, end='\n\n')
if bbqr:
bbqr_out_file = os.path.splitext(out_psbt.name)[0] + ".gif"
vers, parts = split_qrs(res, type_code="U" if base64 else "P", encoding="2",
max_version=20)
qs = [pyqrcode.create(data, error='L', version=vers, mode='alphanumeric') for
data in parts]
frames = []
num_parts = len(parts)
for i in range(num_parts):
xbm = qs[i].xbm(scale=4, quiet_zone=10)
img = ImageChops.invert(Image.open(io.BytesIO(xbm.encode()))).convert('L')
if num_parts > 1:
# add progress bar
pw = img.width // num_parts
lm = (img.width - (pw * num_parts)) // 2
draw = ImageDraw.Draw(img)
h = 4 // 2
y = img.height - h - (4 // 2) - 1
for j in range(num_parts):
draw.rectangle((lm + (j * pw), y, lm + ((j + 1) * pw), y + h), fill=(128 if i != j else 0))
frames.append(img)
if num_parts == 1:
frames[0].save(bbqr_out_file)
else:
frames[0].save(bbqr_out_file, format="gif", save_all=True, loop=0,
duration=250, default_image=False, append_images=frames[1:])
if __name__ == '__main__':
main()

View File

@ -1,3 +1,8 @@
click>=6.7
ecdsa
# or can use python-secp256k1
# BBQr library
git+https://github.com/coinkite/BBQr.git@master#egg=bbqr&subdirectory=python
pyqrcode
Pillow

View File

@ -25,8 +25,11 @@ if __name__ == '__main__':
py_modules=['psbt_faker'],
python_requires='>3.6.0',
install_requires=[
'Click',
'ecdsa',
"click>=6.7",
"ecdsa",
"pyqrcode",
"Pillow",
"bbqr @ git+https://github.com/coinkite/BBQr.git@master#subdirectory=python",
],
entry_points='''
[console_scripts]