From ed28f7350bdad374d92f92b033543f76ca0e61e3 Mon Sep 17 00:00:00 2001 From: Jimmy Song Date: Sun, 1 Mar 2026 04:06:49 -0600 Subject: [PATCH] Add regtest address support to address_to_script_pubkey - Handle bcrt1q (P2WPKH len 44, P2WSH len 64) and bcrt1p (P2TR len 64) prefixes in address_to_script_pubkey() - TxFetcher.get_url() now raises a clear ValueError for regtest instead of an unhelpful KeyError - Add tests for regtest P2WPKH, P2WSH, and P2TR address roundtrips - Add test for TxFetcher regtest error message Co-Authored-By: Claude Opus 4.6 --- buidl/script.py | 13 +++++++------ buidl/test/test_script.py | 27 +++++++++++++++++++++++++++ buidl/test/test_tx.py | 7 +++++++ buidl/tx.py | 5 +++++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/buidl/script.py b/buidl/script.py index 0952b1e..006d6e2 100644 --- a/buidl/script.py +++ b/buidl/script.py @@ -613,15 +613,16 @@ def address_to_script_pubkey(s): # p2sh h160 = decode_base58(s) return P2SHScriptPubKey(h160) - elif s[:4] in ("bc1q", "tb1q"): - if len(s) == 42: + elif s[:4] in ("bc1q", "tb1q") or s[:6] == "bcrt1q": + # regtest p2wpkh is len 44, p2wsh is len 64 (2 extra for "bcrt" vs "bc"/"tb") + if len(s) in (42, 44): # p2wpkh return P2WPKHScriptPubKey(decode_bech32(s)[2]) - elif len(s) == 62: - # p2wskh + elif len(s) in (62, 64): + # p2wsh return P2WSHScriptPubKey(decode_bech32(s)[2]) - elif s[:4] in ("bc1p", "tb1p"): - if len(s) != 62: + elif s[:4] in ("bc1p", "tb1p") or s[:6] == "bcrt1p": + if len(s) not in (62, 64): raise RuntimeError(f"unknown type of address: {s}") # p2tr return P2TRScriptPubKey(decode_bech32(s)[2]) diff --git a/buidl/test/test_script.py b/buidl/test/test_script.py index c0c8de4..7a011d7 100644 --- a/buidl/test/test_script.py +++ b/buidl/test/test_script.py @@ -8,6 +8,7 @@ address_to_script_pubkey, P2PKHScriptPubKey, P2SHScriptPubKey, + P2TRScriptPubKey, P2WPKHScriptPubKey, P2WSHScriptPubKey, RedeemScript, @@ -237,3 +238,29 @@ def test_addr_to_script_pubkey(self): res = address_to_script_pubkey(addr) self.assertEqual(scriptpubkey_type, type(res)) self.assertEqual(res.address(network=network), addr) + + def test_address_to_script_pubkey_regtest(self): + tests = ( + # regtest P2WPKH (bcrt1q..., length 44) + ( + P2WPKHScriptPubKey, + "bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080", + "regtest", + ), + # regtest P2WSH (bcrt1q..., length 64) + ( + P2WSHScriptPubKey, + "bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry", + "regtest", + ), + # regtest P2TR (bcrt1p..., length 64) + ( + P2TRScriptPubKey, + "bcrt1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qg74mmc", + "regtest", + ), + ) + for scriptpubkey_type, addr, network in tests: + res = address_to_script_pubkey(addr) + self.assertEqual(scriptpubkey_type, type(res)) + self.assertEqual(res.address(network=network), addr) diff --git a/buidl/test/test_tx.py b/buidl/test/test_tx.py index 769489a..42ed766 100644 --- a/buidl/test/test_tx.py +++ b/buidl/test/test_tx.py @@ -428,6 +428,13 @@ def test_coinbase_height(self): self.assertIsNone(tx.coinbase_height()) +class TxFetcherRegtestTest(OfflineTestCase): + def test_get_url_regtest(self): + with self.assertRaises(ValueError) as cm: + TxFetcher.get_url("regtest") + self.assertIn("regtest", str(cm.exception)) + + @skipUnless( getenv("INCLUDE_NETWORK_TESTS"), reason="Requires (unreliable) network connection", diff --git a/buidl/tx.py b/buidl/tx.py index 3837759..4ab0be4 100644 --- a/buidl/tx.py +++ b/buidl/tx.py @@ -51,6 +51,11 @@ class TxFetcher: @classmethod def get_url(cls, network="mainnet"): + if network not in URL: + raise ValueError( + f"No URL available for network '{network}'. " + "TxFetcher only supports mainnet, testnet, and signet." + ) return URL[network] @classmethod