diff --git a/.gitignore b/.gitignore index 4d750b5..41a211e 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,4 @@ src/bdkpython/*.so build/ testing-setup-py-simple-example.py -localenvironment/ +.localpythonenv/ diff --git a/README.md b/README.md index 5ee36b5..47aa3cf 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,19 @@ The Python language bindings for the [bitcoindevkit](https://github.com/bitcoindevkit). See the [package on PyPI](https://pypi.org/project/bdkpython/). + +## Local Testing and Usage + +1. Start a Python virtual environment +2. Run one of the build script +3. Create the wheel +4. Install the library +5. Run the tests + +```sh +source .localpythonenv/bin/activate +bash scripts/generate-macos-arm64.sh +.localpythonenv/bin/python3 setup.py bdist_wheel +.localpythonenv/bin/pip3 install ./dist/bdkpython-.whl --force-reinstall +.localpythonenv/bin/python3 -m unittest --verbose +``` diff --git a/bdk-ffi b/bdk-ffi new file mode 160000 index 0000000..0256c27 --- /dev/null +++ b/bdk-ffi @@ -0,0 +1 @@ +Subproject commit 0256c27e8f16b8dc3d7f864f6d0e0355bba38f7b diff --git a/scripts/generate-linux.sh b/scripts/generate-linux.sh index f18fe33..0735d51 100644 --- a/scripts/generate-linux.sh +++ b/scripts/generate-linux.sh @@ -10,7 +10,6 @@ ${PYBIN}/python --version ${PYBIN}/pip install -r requirements.txt cd ./bdk-ffi/bdk-ffi/ -git checkout v1.2.0 rustup default 1.84.1 diff --git a/scripts/generate-macos-arm64.sh b/scripts/generate-macos-arm64.sh index 5f95d0f..8234c4d 100644 --- a/scripts/generate-macos-arm64.sh +++ b/scripts/generate-macos-arm64.sh @@ -10,7 +10,6 @@ python3 --version pip install -r requirements.txt cd ./bdk-ffi/bdk-ffi/ -git checkout v1.2.0 rustup default 1.84.1 rustup target add aarch64-apple-darwin @@ -19,9 +18,9 @@ echo "Generating native binaries..." cargo build --profile release-smaller --target aarch64-apple-darwin echo "Generating bdk.py..." -cargo run --bin uniffi-bindgen generate --library ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib --language python --out-dir ../src/bdkpython/ --no-format +cargo run --bin uniffi-bindgen generate --library ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib --language python --out-dir ../../src/bdkpython/ --no-format echo "Copying libraries libbdkffi.dylib..." -cp ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib ../src/bdkpython/libbdkffi.dylib +cp ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib ../../src/bdkpython/libbdkffi.dylib echo "All done!" diff --git a/scripts/generate-macos-x86_64.sh b/scripts/generate-macos-x86_64.sh index cd8e885..889c4e7 100644 --- a/scripts/generate-macos-x86_64.sh +++ b/scripts/generate-macos-x86_64.sh @@ -10,7 +10,6 @@ python3 --version pip install -r requirements.txt cd ./bdk-ffi/bdk-ffi/ -git checkout v1.2.0 rustup default 1.84.1 rustup target add x86_64-apple-darwin diff --git a/scripts/generate-windows.sh b/scripts/generate-windows.sh index c2b4825..231ad93 100644 --- a/scripts/generate-windows.sh +++ b/scripts/generate-windows.sh @@ -10,7 +10,6 @@ python3 --version pip install -r requirements.txt cd ./bdk-ffi/bdk-ffi/ -git checkout v1.2.0 rustup default 1.84.1 rustup target add x86_64-pc-windows-msvc diff --git a/tests/test_live_kyoto.py b/tests/test_live_kyoto.py deleted file mode 100644 index 651c714..0000000 --- a/tests/test_live_kyoto.py +++ /dev/null @@ -1,62 +0,0 @@ -from bdkpython import Persister, Network, Descriptor, KeychainKind, CbfBuilder, CbfComponents, CbfClient, CbfNode, CbfError, IpAddress, ScanType, Peer, Update, Wallet -import unittest -import os -import asyncio - -network: Network = Network.SIGNET - -ip: IpAddress = IpAddress.from_ipv4(68, 47, 229, 218) -peer: Peer = Peer(address=ip, port=None, v2_transport=False) - -descriptor: Descriptor = Descriptor( - "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/1h/0/*)", - network=network -) -change_descriptor: Descriptor = Descriptor( - "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/1h/1/*)", - network=network -) - -class LiveKyotoTest(unittest.IsolatedAsyncioTestCase): - - def tearDown(self) -> None: - if os.path.exists("./bdk_persistence.sqlite"): - os.remove("./bdk_persistence.sqlite") - if os.path.exists("./data/signet/headers.db"): - os.remove("./data/signet/headers.db") - if os.path.exists("./data/signet/peers.db"): - os.remove("./data/signet/peers.db") - - async def testKyoto(self) -> None: - persister: Persister = Persister.new_in_memory() - wallet: Wallet = Wallet( - descriptor, - change_descriptor, - network, - persister - ) - peers = [peer] - light_client: CbfComponents = CbfBuilder().scan_type(ScanType.NEW()).peers(peers).connections(1).build(wallet) - client: CbfClient = light_client.client - node: CbfNode = light_client.node - async def log_loop(client: CbfClient): - while True: - log = await client.next_log() - print(log) - log_task = asyncio.create_task(log_loop(client)) - node.run() - try: - update: Update = await client.update() - wallet.apply_update(update) - self.assertGreater( - wallet.balance().total.to_sat(), - 0, - f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(KeychainKind.EXTERNAL).address} and try again." - ) - log_task.cancel() - client.shutdown() - except CbfError as e: - raise e - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_live_tx_builder.py b/tests/test_live_tx_builder.py deleted file mode 100644 index aab9f48..0000000 --- a/tests/test_live_tx_builder.py +++ /dev/null @@ -1,111 +0,0 @@ -from bdkpython import Descriptor -from bdkpython import KeychainKind -from bdkpython import Wallet -from bdkpython import EsploraClient -from bdkpython import ScriptAmount -from bdkpython import FullScanRequest -from bdkpython import Address -from bdkpython import Psbt -from bdkpython import TxBuilder -from bdkpython import Persister -from bdkpython import Network -from bdkpython import Amount -from bdkpython import FeeRate - -import unittest -import os - -SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net" -TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud" - -descriptor: Descriptor = Descriptor( - "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/1h/0/*)", - Network.TESTNET -) -change_descriptor: Descriptor = Descriptor( - "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/1h/1/*)", - Network.TESTNET -) - -class LiveTxBuilderTest(unittest.TestCase): - - def tearDown(self) -> None: - if os.path.exists("./bdk_persistence.sqlite"): - os.remove("./bdk_persistence.sqlite") - - def test_tx_builder(self): - connection: Persister = Persister.new_in_memory() - wallet: Wallet = Wallet( - descriptor, - change_descriptor, - Network.SIGNET, - connection - ) - esplora_client: EsploraClient = EsploraClient(url = SIGNET_ESPLORA_URL) - full_scan_request: FullScanRequest = wallet.start_full_scan().build() - update = esplora_client.full_scan( - request=full_scan_request, - stop_gap=10, - parallel_requests=1 - ) - wallet.apply_update(update) - - self.assertGreater( - wallet.balance().total.to_sat(), - 0, - f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(KeychainKind.EXTERNAL).address} and try again." - ) - - recipient = Address( - address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", - network=Network.SIGNET - ) - - psbt = TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=Amount.from_sat(4200)).fee_rate(fee_rate=FeeRate.from_sat_per_vb(2)).finish(wallet) - - self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi") - - def complex_tx_builder(self): - persister: Persister = Persister.new_in_memory() - wallet: Wallet = Wallet( - descriptor, - change_descriptor, - Network.SIGNET, - persister - ) - esplora_client: EsploraClient = EsploraClient(url = SIGNET_ESPLORA_URL) - full_scan_request: FullScanRequest = wallet.start_full_scan().build() - update = esplora_client.full_scan( - request=full_scan_request, - stop_gap=10, - parallel_requests=1 - ) - wallet.apply_update(update) - - self.assertGreater( - wallet.balance().total.to_sat(), - 0, - f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(KeychainKind.EXTERNAL).address} and try again." - ) - - recipient1 = Address( - address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", - network=Network.SIGNET - ) - recipient2 = Address( - address="tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", - network=Network.SIGNET - ) - all_recipients = list( - ScriptAmount(recipient1.script_pubkey, 4200), - ScriptAmount(recipient2.script_pubkey, 4200) - ) - - psbt: Psbt = TxBuilder().set_recipients(all_recipients).fee_rate(fee_rate=FeeRate.from_sat_per_vb(2)).finish(wallet) - wallet.sign(psbt) - - self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi") - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_live_wallet.py b/tests/test_live_wallet.py deleted file mode 100644 index f4f5cf9..0000000 --- a/tests/test_live_wallet.py +++ /dev/null @@ -1,111 +0,0 @@ -from bdkpython import Descriptor -from bdkpython import KeychainKind -from bdkpython import Wallet -from bdkpython import EsploraClient -from bdkpython import FullScanRequest -from bdkpython import Address -from bdkpython import Psbt -from bdkpython import TxBuilder -from bdkpython import Persister -from bdkpython import Network -from bdkpython import Amount -from bdkpython import FeeRate - -import unittest -import os - -SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net" -TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud" - -descriptor: Descriptor = Descriptor( - "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/1h/0/*)", - Network.TESTNET -) -change_descriptor: Descriptor = Descriptor( - "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/1h/1/*)", - Network.TESTNET -) - -class LiveWalletTest(unittest.TestCase): - - def tearDown(self) -> None: - if os.path.exists("./bdk_persistence.sqlite"): - os.remove("./bdk_persistence.sqlite") - - def test_synced_balance(self): - persister: Persister = Persister.new_in_memory() - wallet: Wallet = Wallet( - descriptor, - change_descriptor, - Network.SIGNET, - persister - ) - esplora_client: EsploraClient = EsploraClient(url = SIGNET_ESPLORA_URL) - full_scan_request: FullScanRequest = wallet.start_full_scan().build() - update = esplora_client.full_scan( - request=full_scan_request, - stop_gap=10, - parallel_requests=1 - ) - wallet.apply_update(update) - - self.assertGreater( - wallet.balance().total.to_sat(), - 0, - f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(KeychainKind.EXTERNAL).address} and try again." - ) - - print(f"Transactions count: {len(wallet.transactions())}") - transactions = wallet.transactions()[:3] - for tx in transactions: - sent_and_received = wallet.sent_and_received(tx.transaction) - print(f"Transaction: {tx.transaction.compute_txid()}") - print(f"Sent {sent_and_received.sent.to_sat()}") - print(f"Received {sent_and_received.received.to_sat()}") - - - def test_broadcast_transaction(self): - persister: Persister = Persister.new_in_memory() - wallet: Wallet = Wallet( - descriptor, - change_descriptor, - Network.SIGNET, - persister - ) - esplora_client: EsploraClient = EsploraClient(url = SIGNET_ESPLORA_URL) - full_scan_request: FullScanRequest = wallet.start_full_scan().build() - update = esplora_client.full_scan( - request=full_scan_request, - stop_gap=10, - parallel_requests=1 - ) - wallet.apply_update(update) - - self.assertGreater( - wallet.balance().total.to_sat(), - 0, - f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(KeychainKind.EXTERNAL).address} and try again." - ) - - recipient = Address( - address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", - network=Network.SIGNET - ) - - psbt: Psbt = TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=Amount.from_sat(4200)).fee_rate(fee_rate=FeeRate.from_sat_per_vb(2)).finish(wallet) - self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi") - - walletDidSign = wallet.sign(psbt) - self.assertTrue(walletDidSign) - tx = psbt.extract_tx() - print(f"Transaction Id: {tx.compute_txid()}") - fee = wallet.calculate_fee(tx) - print(f"Transaction Fee: {fee.to_sat()}") - fee_rate = wallet.calculate_fee_rate(tx) - print(f"Transaction Fee Rate: {fee_rate.to_sat_per_vb_ceil()} sat/vB") - - esplora_client.broadcast(tx) - - -if __name__ == '__main__': - unittest.main()