diff --git a/.gitignore b/.gitignore
index c9f0add..a11b87d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ bdkpython.egg-info/
__pycache__/
libbdkffi.dylib
.idea/
+.DS_Store
diff --git a/README.md b/README.md
index 7218b47..e3a9e95 100644
--- a/README.md
+++ b/README.md
@@ -7,21 +7,25 @@ Install the latest release using
```shell
pip install bdkpython
```
+
## Run the tests
```shell
python -m tox
```
+
## Build the package
```shell
python -m build
```
+
## Install locally
```shell
pip install ./dist/bdkpython-0.0.1-py3-none-any.whl
```
+
## Known issues
Note that until the fix is merged upstream in [uniffi-rs](https://github.com/mozilla/uniffi-rs), the `loadIndirect()` function in the `bdk.py` module must be replaced with the following:
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..28ca33b
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+from setuptools import setup, find_packages
+
+setup(
+ name='bdkpython',
+ version='0.0.1',
+ packages=find_packages(where="src"),
+ package_dir={"": "src"},
+ package_data={"bdkpython": ["*.dylib"]},
+ include_package_data=True,
+ zip_safe=False,
+)
diff --git a/src/bdkpython/__init__.py b/src/bdkpython/__init__.py
new file mode 100644
index 0000000..0b0ba05
--- /dev/null
+++ b/src/bdkpython/__init__.py
@@ -0,0 +1 @@
+from bdkpython.bdk import *
diff --git a/src/bdkpython/bdk.py b/src/bdkpython/bdk.py
new file mode 100644
index 0000000..b6e0679
--- /dev/null
+++ b/src/bdkpython/bdk.py
@@ -0,0 +1,1891 @@
+# This file was autogenerated by some hot garbage in the `uniffi` crate.
+# Trust me, you don't want to mess with it!
+
+# Common helper code.
+#
+# Ideally this would live in a separate .py file where it can be unittested etc
+# in isolation, and perhaps even published as a re-useable package.
+#
+# However, it's important that the details of how this helper code works (e.g. the
+# way that different builtin types are passed across the FFI) exactly match what's
+# expected by the rust code on the other side of the interface. In practice right
+# now that means coming from the exact some version of `uniffi` that was used to
+# compile the rust component. The easiest way to ensure this is to bundle the Python
+# helpers directly inline like we're doing here.
+
+import os
+import sys
+import ctypes
+import enum
+import struct
+import contextlib
+import datetime
+
+
+class RustBuffer(ctypes.Structure):
+ _fields_ = [
+ ("capacity", ctypes.c_int32),
+ ("len", ctypes.c_int32),
+ ("data", ctypes.POINTER(ctypes.c_char)),
+ ]
+
+ @staticmethod
+ def alloc(size):
+ return rust_call(_UniFFILib.ffi_bdk_b9b3_rustbuffer_alloc, size)
+
+ @staticmethod
+ def reserve(rbuf, additional):
+ return rust_call(_UniFFILib.ffi_bdk_b9b3_rustbuffer_reserve, rbuf, additional)
+
+ def free(self):
+ return rust_call(_UniFFILib.ffi_bdk_b9b3_rustbuffer_free, self)
+
+ def __str__(self):
+ return "RustBuffer(capacity={}, len={}, data={})".format(
+ self.capacity,
+ self.len,
+ self.data[0:self.len]
+ )
+
+ @contextlib.contextmanager
+ def allocWithBuilder():
+ """Context-manger to allocate a buffer using a RustBufferBuilder.
+
+ The allocated buffer will be automatically freed if an error occurs, ensuring that
+ we don't accidentally leak it.
+ """
+ builder = RustBufferBuilder()
+ try:
+ yield builder
+ except:
+ builder.discard()
+ raise
+
+ @contextlib.contextmanager
+ def consumeWithStream(self):
+ """Context-manager to consume a buffer using a RustBufferStream.
+
+ The RustBuffer will be freed once the context-manager exits, ensuring that we don't
+ leak it even if an error occurs.
+ """
+ try:
+ s = RustBufferStream(self)
+ yield s
+ if s.remaining() != 0:
+ raise RuntimeError("junk data left in buffer after consuming")
+ finally:
+ self.free()
+
+
+class ForeignBytes(ctypes.Structure):
+ _fields_ = [
+ ("len", ctypes.c_int32),
+ ("data", ctypes.POINTER(ctypes.c_char)),
+ ]
+
+ def __str__(self):
+ return "ForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len])
+
+
+class RustBufferStream(object):
+ """
+ Helper for structured reading of bytes from a RustBuffer
+ """
+
+ def __init__(self, rbuf):
+ self.rbuf = rbuf
+ self.offset = 0
+
+ def remaining(self):
+ return self.rbuf.len - self.offset
+
+ def _unpack_from(self, size, format):
+ if self.offset + size > self.rbuf.len:
+ raise InternalError("read past end of rust buffer")
+ value = struct.unpack(format, self.rbuf.data[self.offset:self.offset+size])[0]
+ self.offset += size
+ return value
+
+ def read(self, size):
+ if self.offset + size > self.rbuf.len:
+ raise InternalError("read past end of rust buffer")
+ data = self.rbuf.data[self.offset:self.offset+size]
+ self.offset += size
+ return data
+
+ def readI8(self):
+ return self._unpack_from(1, ">b")
+
+ def readU8(self):
+ return self._unpack_from(1, ">B")
+
+ def readI16(self):
+ return self._unpack_from(2, ">h")
+
+ def readU16(self):
+ return self._unpack_from(2, ">H")
+
+ def readI32(self):
+ return self._unpack_from(4, ">i")
+
+ def readU32(self):
+ return self._unpack_from(4, ">I")
+
+ def readI64(self):
+ return self._unpack_from(8, ">q")
+
+ def readU64(self):
+ return self._unpack_from(8, ">Q")
+
+ def readFloat(self):
+ v = self._unpack_from(4, ">f")
+ return v
+
+ def readDouble(self):
+ return self._unpack_from(8, ">d")
+
+
+class RustBufferBuilder(object):
+ """
+ Helper for structured writing of bytes into a RustBuffer.
+ """
+
+ def __init__(self):
+ self.rbuf = RustBuffer.alloc(16)
+ self.rbuf.len = 0
+
+ def finalize(self):
+ rbuf = self.rbuf
+ self.rbuf = None
+ return rbuf
+
+ def discard(self):
+ if self.rbuf is not None:
+ rbuf = self.finalize()
+ rbuf.free()
+
+ @contextlib.contextmanager
+ def _reserve(self, numBytes):
+ if self.rbuf.len + numBytes > self.rbuf.capacity:
+ self.rbuf = RustBuffer.reserve(self.rbuf, numBytes)
+ yield None
+ self.rbuf.len += numBytes
+
+ def _pack_into(self, size, format, value):
+ with self._reserve(size):
+ # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out.
+ for i, byte in enumerate(struct.pack(format, value)):
+ self.rbuf.data[self.rbuf.len + i] = byte
+
+ def write(self, value):
+ with self._reserve(len(value)):
+ for i, byte in enumerate(value):
+ self.rbuf.data[self.rbuf.len + i] = byte
+
+ def writeI8(self, v):
+ self._pack_into(1, ">b", v)
+
+ def writeU8(self, v):
+ self._pack_into(1, ">B", v)
+
+ def writeI16(self, v):
+ self._pack_into(2, ">h", v)
+
+ def writeU16(self, v):
+ self._pack_into(2, ">H", v)
+
+ def writeI32(self, v):
+ self._pack_into(4, ">i", v)
+
+ def writeU32(self, v):
+ self._pack_into(4, ">I", v)
+
+ def writeI64(self, v):
+ self._pack_into(8, ">q", v)
+
+ def writeU64(self, v):
+ self._pack_into(8, ">Q", v)
+
+ def writeFloat(self, v):
+ self._pack_into(4, ">f", v)
+
+ def writeDouble(self, v):
+ self._pack_into(8, ">d", v)
+# A handful of classes and functions to support the generated data structures.
+# This would be a good candidate for isolating in its own ffi-support lib.
+
+class InternalError(Exception):
+ pass
+
+class RustCallStatus(ctypes.Structure):
+ """
+ Error runtime.
+ """
+ _fields_ = [
+ ("code", ctypes.c_int8),
+ ("error_buf", RustBuffer),
+ ]
+
+ # These match the values from the uniffi::rustcalls module
+ CALL_SUCCESS = 0
+ CALL_ERROR = 1
+ CALL_PANIC = 2
+
+ def __str__(self):
+ if self.code == RustCallStatus.CALL_SUCCESS:
+ return "RustCallStatus(CALL_SUCCESS)"
+ elif self.code == RustCallStatus.CALL_ERROR:
+ return "RustCallStatus(CALL_ERROR)"
+ elif self.code == RustCallStatus.CALL_PANIC:
+ return "RustCallStatus(CALL_SUCCESS)"
+ else:
+ return "RustCallStatus()"
+
+def rust_call(fn, *args):
+ # Call a rust function
+ return rust_call_with_error(None, fn, *args)
+
+def rust_call_with_error(error_class, fn, *args):
+ # Call a rust function and handle any errors
+ #
+ # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code.
+ # error_class must be set to the error class that corresponds to the result.
+ call_status = RustCallStatus(code=RustCallStatus.CALL_SUCCESS, error_buf=RustBuffer(0, 0, None))
+
+ args_with_error = args + (ctypes.byref(call_status),)
+ result = fn(*args_with_error)
+ if call_status.code == RustCallStatus.CALL_SUCCESS:
+ return result
+ elif call_status.code == RustCallStatus.CALL_ERROR:
+ if error_class is None:
+ call_status.err_buf.contents.free()
+ raise InternalError("rust_call_with_error: CALL_ERROR, but no error class set")
+ else:
+ raise error_class._lift(call_status.error_buf)
+ elif call_status.code == RustCallStatus.CALL_PANIC:
+ # When the rust code sees a panic, it tries to construct a RustBuffer
+ # with the message. But if that code panics, then it just sends back
+ # an empty buffer.
+ if call_status.error_buf.len > 0:
+ msg = FfiConverterString._lift(call_status.error_buf)
+ else:
+ msg = "Unknown rust panic"
+ raise InternalError(msg)
+ else:
+ raise InternalError("Invalid RustCallStatus code: {}".format(
+ call_status.code))
+
+# A function pointer for a callback as defined by UniFFI.
+# Rust definition `fn(handle: u64, method: u32, args: RustBuffer, buf_ptr: *mut RustBuffer) -> int`
+FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, RustBuffer, ctypes.POINTER(RustBuffer))
+# Types conforming to `Primitive` pass themselves directly over the FFI.
+class Primitive:
+ @classmethod
+ def _lift(cls, value):
+ return value
+
+ @classmethod
+ def _lower(cls, value):
+ return value
+
+# Helper class for new types that will always go through a RustBuffer.
+# Classes should inherit from this and implement the `_read` static method
+# and `_write` instance methods.
+class ViaFfiUsingByteBuffer:
+ @classmethod
+ def _lift(cls, rbuf):
+ with rbuf.consumeWithStream() as stream:
+ return cls._read(stream)
+
+ def _lower(self):
+ with RustBuffer.allocWithBuilder() as builder:
+ self._write(builder)
+ return builder.finalize()
+
+# Helper class for wrapper types that will always go through a RustBuffer.
+# Classes should inherit from this and implement the `_read` and `_write` static methods.
+class FfiConverterUsingByteBuffer:
+ @classmethod
+ def _lift(cls, rbuf):
+ with rbuf.consumeWithStream() as stream:
+ return cls._read(stream)
+
+ @classmethod
+ def _lower(cls, value):
+ with RustBuffer.allocWithBuilder() as builder:
+ cls._write(value, builder)
+ return builder.finalize()
+
+# Helpers for structural types.
+
+class FfiConverterSequence:
+ @staticmethod
+ def _write(value, buf, writeItem):
+ items = len(value)
+ buf.writeI32(items)
+ for item in value:
+ writeItem(item, buf)
+
+ @staticmethod
+ def _read(buf, readItem):
+ count = buf.readI32()
+ if count < 0:
+ raise InternalError("Unexpected negative sequence length")
+
+ items = []
+ while count > 0:
+ items.append(readItem(buf))
+ count -= 1
+ return items
+
+class FfiConverterOptional:
+ @staticmethod
+ def _write(value, buf, writeItem):
+ if value is None:
+ buf.writeU8(0)
+ return
+
+ buf.writeU8(1)
+ writeItem(value, buf)
+
+ @staticmethod
+ def _read(buf, readItem):
+ flag = buf.readU8()
+ if flag == 0:
+ return None
+ elif flag == 1:
+ return readItem(buf)
+ else:
+ raise InternalError("Unexpected flag byte for optional type")
+
+class FfiConverterDictionary:
+ @staticmethod
+ def _write(items, buf, writeItem):
+ buf.writeI32(len(items))
+ for (key, value) in items.items():
+ writeItem(key, value, buf)
+
+ @staticmethod
+ def _read(buf, readItem):
+ count = buf.readI32()
+ if count < 0:
+ raise InternalError("Unexpected negative map size")
+ items = {}
+ while count > 0:
+ key, value = readItem(buf)
+ items[key] = value
+ count -= 1
+ return items
+
+# Contains loading, initialization code,
+# and the FFI Function declarations in a com.sun.jna.Library.
+# This is how we find and load the dynamic library provided by the component.
+# For now we just look it up by name.
+#
+# XXX TODO: This will probably grow some magic for resolving megazording in future.
+# E.g. we might start by looking for the named component in `libuniffi.so` and if
+# that fails, fall back to loading it separately from `lib${componentName}.so`.
+
+def loadIndirect():
+ if sys.platform == "linux":
+ # libname = "lib{}.so"
+ libname = os.path.join(os.path.dirname(__file__), "lib{}.so")
+ elif sys.platform == "darwin":
+ # libname = "lib{}.dylib"
+ libname = os.path.join(os.path.dirname(__file__), "lib{}.dylib")
+ elif sys.platform.startswith("win"):
+ # As of python3.8, ctypes does not seem to search $PATH when loading DLLs.
+ # We could use `os.add_dll_directory` to configure the search path, but
+ # it doesn't feel right to mess with application-wide settings. Let's
+ # assume that the `.dll` is next to the `.py` file and load by full path.
+ libname = os.path.join(
+ os.path.dirname(__file__),
+ "{}.dll",
+ )
+ return getattr(ctypes.cdll, libname.format("bdkffi"))
+
+# A ctypes library to expose the extern-C FFI definitions.
+# This is an implementation detail which will be called internally by the public API.
+
+_UniFFILib = loadIndirect()
+_UniFFILib.ffi_bdk_b9b3_OfflineWallet_object_free.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.ffi_bdk_b9b3_OfflineWallet_object_free.restype = None
+_UniFFILib.bdk_b9b3_OfflineWallet_new.argtypes = (
+ RustBuffer,
+ RustBuffer,
+ RustBuffer,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OfflineWallet_new.restype = ctypes.c_void_p
+_UniFFILib.bdk_b9b3_OfflineWallet_get_new_address.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OfflineWallet_get_new_address.restype = RustBuffer
+_UniFFILib.bdk_b9b3_OfflineWallet_get_last_unused_address.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OfflineWallet_get_last_unused_address.restype = RustBuffer
+_UniFFILib.bdk_b9b3_OfflineWallet_get_balance.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OfflineWallet_get_balance.restype = ctypes.c_uint64
+_UniFFILib.bdk_b9b3_OfflineWallet_sign.argtypes = (
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OfflineWallet_sign.restype = None
+_UniFFILib.bdk_b9b3_OfflineWallet_get_transactions.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OfflineWallet_get_transactions.restype = RustBuffer
+_UniFFILib.ffi_bdk_b9b3_OnlineWallet_object_free.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.ffi_bdk_b9b3_OnlineWallet_object_free.restype = None
+_UniFFILib.bdk_b9b3_OnlineWallet_new.argtypes = (
+ RustBuffer,
+ RustBuffer,
+ RustBuffer,
+ RustBuffer,
+ RustBuffer,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_new.restype = ctypes.c_void_p
+_UniFFILib.bdk_b9b3_OnlineWallet_get_new_address.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_get_new_address.restype = RustBuffer
+_UniFFILib.bdk_b9b3_OnlineWallet_get_last_unused_address.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_get_last_unused_address.restype = RustBuffer
+_UniFFILib.bdk_b9b3_OnlineWallet_get_balance.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_get_balance.restype = ctypes.c_uint64
+_UniFFILib.bdk_b9b3_OnlineWallet_sign.argtypes = (
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_sign.restype = None
+_UniFFILib.bdk_b9b3_OnlineWallet_get_transactions.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_get_transactions.restype = RustBuffer
+_UniFFILib.bdk_b9b3_OnlineWallet_get_network.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_get_network.restype = RustBuffer
+_UniFFILib.bdk_b9b3_OnlineWallet_sync.argtypes = (
+ ctypes.c_void_p,
+ ctypes.c_uint64,
+ RustBuffer,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_sync.restype = None
+_UniFFILib.bdk_b9b3_OnlineWallet_broadcast.argtypes = (
+ ctypes.c_void_p,
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_OnlineWallet_broadcast.restype = RustBuffer
+_UniFFILib.ffi_bdk_b9b3_PartiallySignedBitcoinTransaction_object_free.argtypes = (
+ ctypes.c_void_p,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.ffi_bdk_b9b3_PartiallySignedBitcoinTransaction_object_free.restype = None
+_UniFFILib.bdk_b9b3_PartiallySignedBitcoinTransaction_new.argtypes = (
+ ctypes.c_void_p,
+ RustBuffer,
+ ctypes.c_uint64,
+ RustBuffer,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_PartiallySignedBitcoinTransaction_new.restype = ctypes.c_void_p
+_UniFFILib.ffi_bdk_b9b3_BdkProgress_init_callback.argtypes = (
+ FOREIGN_CALLBACK_T,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.ffi_bdk_b9b3_BdkProgress_init_callback.restype = None
+_UniFFILib.bdk_b9b3_generate_extended_key.argtypes = (
+ RustBuffer,
+ RustBuffer,
+ RustBuffer,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_generate_extended_key.restype = RustBuffer
+_UniFFILib.bdk_b9b3_restore_extended_key.argtypes = (
+ RustBuffer,
+ RustBuffer,
+ RustBuffer,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.bdk_b9b3_restore_extended_key.restype = RustBuffer
+_UniFFILib.ffi_bdk_b9b3_rustbuffer_alloc.argtypes = (
+ ctypes.c_int32,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.ffi_bdk_b9b3_rustbuffer_alloc.restype = RustBuffer
+_UniFFILib.ffi_bdk_b9b3_rustbuffer_from_bytes.argtypes = (
+ ForeignBytes,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.ffi_bdk_b9b3_rustbuffer_from_bytes.restype = RustBuffer
+_UniFFILib.ffi_bdk_b9b3_rustbuffer_free.argtypes = (
+ RustBuffer,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.ffi_bdk_b9b3_rustbuffer_free.restype = None
+_UniFFILib.ffi_bdk_b9b3_rustbuffer_reserve.argtypes = (
+ RustBuffer,
+ ctypes.c_int32,
+ ctypes.POINTER(RustCallStatus),
+)
+_UniFFILib.ffi_bdk_b9b3_rustbuffer_reserve.restype = RustBuffer
+
+# Public interface members begin here.
+
+import threading
+
+class ConcurrentHandleMap:
+ """
+ A map where inserting, getting and removing data is synchronized with a lock.
+ """
+
+ def __init__(self):
+ # type Handle = int
+ self._left_map = {} # type: Dict[Handle, Any]
+ self._right_map = {} # type: Dict[Any, Handle]
+
+ self._lock = threading.Lock()
+ self._current_handle = 0
+ self._stride = 1
+
+
+ def insert(self, obj):
+ with self._lock:
+ if obj in self._right_map:
+ return self._right_map[obj]
+ else:
+ handle = self._current_handle
+ self._current_handle += self._stride
+ self._left_map[handle] = obj
+ self._right_map[obj] = handle
+ return handle
+
+ def get(self, handle):
+ with self._lock:
+ return self._left_map.get(handle)
+
+ def remove(self, handle):
+ with self._lock:
+ if handle in self._left_map:
+ obj = self._left_map.pop(handle)
+ del self._right_map[obj]
+ return obj
+
+# Magic number for the Rust proxy to call using the same mechanism as every other method,
+# to free the callback once it's dropped by Rust.
+IDX_CALLBACK_FREE = 0
+
+class FfiConverterCallbackInterface:
+ _handle_map = ConcurrentHandleMap()
+
+ def __init__(self, cb):
+ self._foreign_callback = cb
+
+ def drop(self, handle):
+ self.__class__._handle_map.remove(handle)
+
+ @classmethod
+ def _lift(cls, handle):
+ obj = cls._handle_map.get(handle)
+ if not obj:
+ raise InternalError("The object in the handle map has been dropped already")
+
+ return obj
+
+ @classmethod
+ def _read(cls, buf):
+ handle = buf.readU64()
+ cls._lift(handle)
+
+ @classmethod
+ def _lower(cls, cb):
+ handle = cls._handle_map.insert(cb)
+ return handle
+
+ @classmethod
+ def _write(cls, cb, buf):
+ buf.writeU64(cls._lower(cb))
+
+
+
+class Network(ViaFfiUsingByteBuffer, enum.Enum):
+ BITCOIN = 1
+ TESTNET = 2
+ SIGNET = 3
+ REGTEST = 4
+
+
+ @staticmethod
+ def _read(buf):
+ variant = buf.readI32()
+ if variant == 1:
+ return Network.BITCOIN
+ if variant == 2:
+ return Network.TESTNET
+ if variant == 3:
+ return Network.SIGNET
+ if variant == 4:
+ return Network.REGTEST
+
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ def _write(self, buf):
+ if self is Network.BITCOIN:
+ i = 1
+ buf.writeI32(1)
+ if self is Network.TESTNET:
+ i = 2
+ buf.writeI32(2)
+ if self is Network.SIGNET:
+ i = 3
+ buf.writeI32(3)
+ if self is Network.REGTEST:
+ i = 4
+ buf.writeI32(4)
+
+
+
+
+
+
+
+class DatabaseConfig(ViaFfiUsingByteBuffer, object):
+ def __init__(self):
+ raise RuntimeError("DatabaseConfig cannot be instantiated directly")
+
+ # Each enum variant is a nested class of the enum itself.
+ class MEMORY(object):
+ def __init__(self,junk ):
+
+ self.junk = junk
+
+
+ def __str__(self):
+ return "DatabaseConfig.MEMORY(junk={} )".format(self.junk )
+
+ def __eq__(self, other):
+ if not other.is_memory():
+ return False
+ if self.junk != other.junk:
+ return False
+ return True
+ class SLED(object):
+ def __init__(self,config ):
+
+ self.config = config
+
+
+ def __str__(self):
+ return "DatabaseConfig.SLED(config={} )".format(self.config )
+
+ def __eq__(self, other):
+ if not other.is_sled():
+ return False
+ if self.config != other.config:
+ return False
+ return True
+
+
+ # For each variant, we have an `is_NAME` method for easily checking
+ # whether an instance is that variant.
+ def is_memory(self):
+ return isinstance(self, DatabaseConfig.MEMORY)
+ def is_sled(self):
+ return isinstance(self, DatabaseConfig.SLED)
+
+
+ @classmethod
+ def _read(cls, buf):
+ variant = buf.readI32()
+
+ if variant == 1:
+ return cls.MEMORY(
+ junk=FfiConverterString._read(buf),
+ )
+ if variant == 2:
+ return cls.SLED(
+ config=SledDbConfiguration._read(buf),
+ )
+
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ def _write(self, buf):
+ if self.is_memory():
+ buf.writeI32(1)
+ FfiConverterString._write(self.junk, buf)
+ if self.is_sled():
+ buf.writeI32(2)
+ self.config._write(buf)
+
+
+# Now, a little trick - we make each nested variant class be a subclass of the main
+# enum class, so that method calls and instance checks etc will work intuitively.
+# We might be able to do this a little more neatly with a metaclass, but this'll do.
+DatabaseConfig.MEMORY = type("DatabaseConfig.MEMORY", (DatabaseConfig.MEMORY, DatabaseConfig,), {})
+DatabaseConfig.SLED = type("DatabaseConfig.SLED", (DatabaseConfig.SLED, DatabaseConfig,), {})
+
+
+
+
+
+
+
+class Transaction(ViaFfiUsingByteBuffer, object):
+ def __init__(self):
+ raise RuntimeError("Transaction cannot be instantiated directly")
+
+ # Each enum variant is a nested class of the enum itself.
+ class UNCONFIRMED(object):
+ def __init__(self,details ):
+
+ self.details = details
+
+
+ def __str__(self):
+ return "Transaction.UNCONFIRMED(details={} )".format(self.details )
+
+ def __eq__(self, other):
+ if not other.is_unconfirmed():
+ return False
+ if self.details != other.details:
+ return False
+ return True
+ class CONFIRMED(object):
+ def __init__(self,details, confirmation ):
+
+ self.details = details
+ self.confirmation = confirmation
+
+
+ def __str__(self):
+ return "Transaction.CONFIRMED(details={}, confirmation={} )".format(self.details, self.confirmation )
+
+ def __eq__(self, other):
+ if not other.is_confirmed():
+ return False
+ if self.details != other.details:
+ return False
+ if self.confirmation != other.confirmation:
+ return False
+ return True
+
+
+ # For each variant, we have an `is_NAME` method for easily checking
+ # whether an instance is that variant.
+ def is_unconfirmed(self):
+ return isinstance(self, Transaction.UNCONFIRMED)
+ def is_confirmed(self):
+ return isinstance(self, Transaction.CONFIRMED)
+
+
+ @classmethod
+ def _read(cls, buf):
+ variant = buf.readI32()
+
+ if variant == 1:
+ return cls.UNCONFIRMED(
+ details=TransactionDetails._read(buf),
+ )
+ if variant == 2:
+ return cls.CONFIRMED(
+ details=TransactionDetails._read(buf),
+ confirmation=Confirmation._read(buf),
+ )
+
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ def _write(self, buf):
+ if self.is_unconfirmed():
+ buf.writeI32(1)
+ self.details._write(buf)
+ if self.is_confirmed():
+ buf.writeI32(2)
+ self.details._write(buf)
+ self.confirmation._write(buf)
+
+
+# Now, a little trick - we make each nested variant class be a subclass of the main
+# enum class, so that method calls and instance checks etc will work intuitively.
+# We might be able to do this a little more neatly with a metaclass, but this'll do.
+Transaction.UNCONFIRMED = type("Transaction.UNCONFIRMED", (Transaction.UNCONFIRMED, Transaction,), {})
+Transaction.CONFIRMED = type("Transaction.CONFIRMED", (Transaction.CONFIRMED, Transaction,), {})
+
+
+
+
+
+
+
+class BlockchainConfig(ViaFfiUsingByteBuffer, object):
+ def __init__(self):
+ raise RuntimeError("BlockchainConfig cannot be instantiated directly")
+
+ # Each enum variant is a nested class of the enum itself.
+ class ELECTRUM(object):
+ def __init__(self,config ):
+
+ self.config = config
+
+
+ def __str__(self):
+ return "BlockchainConfig.ELECTRUM(config={} )".format(self.config )
+
+ def __eq__(self, other):
+ if not other.is_electrum():
+ return False
+ if self.config != other.config:
+ return False
+ return True
+ class ESPLORA(object):
+ def __init__(self,config ):
+
+ self.config = config
+
+
+ def __str__(self):
+ return "BlockchainConfig.ESPLORA(config={} )".format(self.config )
+
+ def __eq__(self, other):
+ if not other.is_esplora():
+ return False
+ if self.config != other.config:
+ return False
+ return True
+
+
+ # For each variant, we have an `is_NAME` method for easily checking
+ # whether an instance is that variant.
+ def is_electrum(self):
+ return isinstance(self, BlockchainConfig.ELECTRUM)
+ def is_esplora(self):
+ return isinstance(self, BlockchainConfig.ESPLORA)
+
+
+ @classmethod
+ def _read(cls, buf):
+ variant = buf.readI32()
+
+ if variant == 1:
+ return cls.ELECTRUM(
+ config=ElectrumConfig._read(buf),
+ )
+ if variant == 2:
+ return cls.ESPLORA(
+ config=EsploraConfig._read(buf),
+ )
+
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ def _write(self, buf):
+ if self.is_electrum():
+ buf.writeI32(1)
+ self.config._write(buf)
+ if self.is_esplora():
+ buf.writeI32(2)
+ self.config._write(buf)
+
+
+# Now, a little trick - we make each nested variant class be a subclass of the main
+# enum class, so that method calls and instance checks etc will work intuitively.
+# We might be able to do this a little more neatly with a metaclass, but this'll do.
+BlockchainConfig.ELECTRUM = type("BlockchainConfig.ELECTRUM", (BlockchainConfig.ELECTRUM, BlockchainConfig,), {})
+BlockchainConfig.ESPLORA = type("BlockchainConfig.ESPLORA", (BlockchainConfig.ESPLORA, BlockchainConfig,), {})
+
+
+
+
+
+
+class MnemonicType(ViaFfiUsingByteBuffer, enum.Enum):
+ WORDS12 = 1
+ WORDS15 = 2
+ WORDS18 = 3
+ WORDS21 = 4
+ WORDS24 = 5
+
+
+ @staticmethod
+ def _read(buf):
+ variant = buf.readI32()
+ if variant == 1:
+ return MnemonicType.WORDS12
+ if variant == 2:
+ return MnemonicType.WORDS15
+ if variant == 3:
+ return MnemonicType.WORDS18
+ if variant == 4:
+ return MnemonicType.WORDS21
+ if variant == 5:
+ return MnemonicType.WORDS24
+
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ def _write(self, buf):
+ if self is MnemonicType.WORDS12:
+ i = 1
+ buf.writeI32(1)
+ if self is MnemonicType.WORDS15:
+ i = 2
+ buf.writeI32(2)
+ if self is MnemonicType.WORDS18:
+ i = 3
+ buf.writeI32(3)
+ if self is MnemonicType.WORDS21:
+ i = 4
+ buf.writeI32(4)
+ if self is MnemonicType.WORDS24:
+ i = 5
+ buf.writeI32(5)
+
+
+
+
+
+def generate_extended_key(network,mnemonic_type,password):
+ network = network
+ mnemonic_type = mnemonic_type
+ password = (None if password is None else password)
+ _retval = rust_call_with_error(BdkError,_UniFFILib.bdk_b9b3_generate_extended_key,network._lower(),mnemonic_type._lower(),FfiConverterOptionalString._lower(password))
+ return ExtendedKeyInfo._lift(_retval)
+
+
+
+def restore_extended_key(network,mnemonic,password):
+ network = network
+ mnemonic = mnemonic
+ password = (None if password is None else password)
+ _retval = rust_call_with_error(BdkError,_UniFFILib.bdk_b9b3_restore_extended_key,network._lower(),FfiConverterString._lower(mnemonic),FfiConverterOptionalString._lower(password))
+ return ExtendedKeyInfo._lift(_retval)
+
+
+
+class OfflineWallet(object):
+ def __init__(self, descriptor,network,database_config):
+ descriptor = descriptor
+ network = network
+ database_config = database_config
+ self._pointer = rust_call_with_error(BdkError,_UniFFILib.bdk_b9b3_OfflineWallet_new,FfiConverterString._lower(descriptor),network._lower(),database_config._lower())
+
+ def __del__(self):
+ # In case of partial initialization of instances.
+ pointer = getattr(self, "_pointer", None)
+ if pointer is not None:
+ rust_call(_UniFFILib.ffi_bdk_b9b3_OfflineWallet_object_free, pointer)
+
+ # Used by alternative constructors or any methods which return this type.
+ @classmethod
+ def _make_instance_(cls, pointer):
+ # Lightly yucky way to bypass the usual __init__ logic
+ # and just create a new instance with the required pointer.
+ inst = cls.__new__(cls)
+ inst._pointer = pointer
+ return inst
+
+
+
+ def get_new_address(self, ):
+ _retval = rust_call(_UniFFILib.bdk_b9b3_OfflineWallet_get_new_address,self._pointer,)
+ return FfiConverterString._lift(_retval)
+
+ def get_last_unused_address(self, ):
+ _retval = rust_call(_UniFFILib.bdk_b9b3_OfflineWallet_get_last_unused_address,self._pointer,)
+ return FfiConverterString._lift(_retval)
+
+ def get_balance(self, ):
+ _retval = rust_call_with_error(
+ BdkError,_UniFFILib.bdk_b9b3_OfflineWallet_get_balance,self._pointer,)
+ return FfiConverterUInt64._lift(_retval)
+
+ def sign(self, psbt):
+ psbt = psbt
+ rust_call_with_error(
+ BdkError,_UniFFILib.bdk_b9b3_OfflineWallet_sign,self._pointer,psbt._lower())
+
+ def get_transactions(self, ):
+ _retval = rust_call_with_error(
+ BdkError,_UniFFILib.bdk_b9b3_OfflineWallet_get_transactions,self._pointer,)
+ return FfiConverterSequenceEnumTransaction._lift(_retval)
+
+
+
+ @classmethod
+ def _read(cls, buf):
+ ptr = buf.readU64()
+ if ptr == 0:
+ raise InternalError("Raw pointer value was null")
+ return cls._lift(ptr)
+
+ @classmethod
+ def _write(cls, value, buf):
+ if not isinstance(value, OfflineWallet):
+ raise TypeError("Expected OfflineWallet instance, {} found".format(value.__class__.__name__))
+ buf.writeU64(value._lower())
+
+ @classmethod
+ def _lift(cls, pointer):
+ return cls._make_instance_(pointer)
+
+ def _lower(self):
+ return self._pointer
+
+
+class OnlineWallet(object):
+ def __init__(self, descriptor,change_descriptor,network,database_config,blockchain_config):
+ descriptor = descriptor
+ change_descriptor = (None if change_descriptor is None else change_descriptor)
+ network = network
+ database_config = database_config
+ blockchain_config = blockchain_config
+ self._pointer = rust_call_with_error(BdkError,_UniFFILib.bdk_b9b3_OnlineWallet_new,FfiConverterString._lower(descriptor),FfiConverterOptionalString._lower(change_descriptor),network._lower(),database_config._lower(),blockchain_config._lower())
+
+ def __del__(self):
+ # In case of partial initialization of instances.
+ pointer = getattr(self, "_pointer", None)
+ if pointer is not None:
+ rust_call(_UniFFILib.ffi_bdk_b9b3_OnlineWallet_object_free, pointer)
+
+ # Used by alternative constructors or any methods which return this type.
+ @classmethod
+ def _make_instance_(cls, pointer):
+ # Lightly yucky way to bypass the usual __init__ logic
+ # and just create a new instance with the required pointer.
+ inst = cls.__new__(cls)
+ inst._pointer = pointer
+ return inst
+
+
+
+ def get_new_address(self, ):
+ _retval = rust_call(_UniFFILib.bdk_b9b3_OnlineWallet_get_new_address,self._pointer,)
+ return FfiConverterString._lift(_retval)
+
+ def get_last_unused_address(self, ):
+ _retval = rust_call(_UniFFILib.bdk_b9b3_OnlineWallet_get_last_unused_address,self._pointer,)
+ return FfiConverterString._lift(_retval)
+
+ def get_balance(self, ):
+ _retval = rust_call_with_error(
+ BdkError,_UniFFILib.bdk_b9b3_OnlineWallet_get_balance,self._pointer,)
+ return FfiConverterUInt64._lift(_retval)
+
+ def sign(self, psbt):
+ psbt = psbt
+ rust_call_with_error(
+ BdkError,_UniFFILib.bdk_b9b3_OnlineWallet_sign,self._pointer,psbt._lower())
+
+ def get_transactions(self, ):
+ _retval = rust_call_with_error(
+ BdkError,_UniFFILib.bdk_b9b3_OnlineWallet_get_transactions,self._pointer,)
+ return FfiConverterSequenceEnumTransaction._lift(_retval)
+
+ def get_network(self, ):
+ _retval = rust_call(_UniFFILib.bdk_b9b3_OnlineWallet_get_network,self._pointer,)
+ return Network._lift(_retval)
+
+ def sync(self, progress_update,max_address_param):
+ progress_update = progress_update
+ max_address_param = (None if max_address_param is None else int(max_address_param))
+ rust_call_with_error(
+ BdkError,_UniFFILib.bdk_b9b3_OnlineWallet_sync,self._pointer,FfiConverterCallbackInterfaceBdkProgress._lower(progress_update),FfiConverterOptionalUInt32._lower(max_address_param))
+
+ def broadcast(self, psbt):
+ psbt = psbt
+ _retval = rust_call_with_error(
+ BdkError,_UniFFILib.bdk_b9b3_OnlineWallet_broadcast,self._pointer,psbt._lower())
+ return Transaction._lift(_retval)
+
+
+
+ @classmethod
+ def _read(cls, buf):
+ ptr = buf.readU64()
+ if ptr == 0:
+ raise InternalError("Raw pointer value was null")
+ return cls._lift(ptr)
+
+ @classmethod
+ def _write(cls, value, buf):
+ if not isinstance(value, OnlineWallet):
+ raise TypeError("Expected OnlineWallet instance, {} found".format(value.__class__.__name__))
+ buf.writeU64(value._lower())
+
+ @classmethod
+ def _lift(cls, pointer):
+ return cls._make_instance_(pointer)
+
+ def _lower(self):
+ return self._pointer
+
+
+class PartiallySignedBitcoinTransaction(object):
+ def __init__(self, wallet,recipient,amount,fee_rate):
+ wallet = wallet
+ recipient = recipient
+ amount = int(amount)
+ fee_rate = (None if fee_rate is None else float(fee_rate))
+ self._pointer = rust_call_with_error(BdkError,_UniFFILib.bdk_b9b3_PartiallySignedBitcoinTransaction_new,wallet._lower(),FfiConverterString._lower(recipient),FfiConverterUInt64._lower(amount),FfiConverterOptionalFloat._lower(fee_rate))
+
+ def __del__(self):
+ # In case of partial initialization of instances.
+ pointer = getattr(self, "_pointer", None)
+ if pointer is not None:
+ rust_call(_UniFFILib.ffi_bdk_b9b3_PartiallySignedBitcoinTransaction_object_free, pointer)
+
+ # Used by alternative constructors or any methods which return this type.
+ @classmethod
+ def _make_instance_(cls, pointer):
+ # Lightly yucky way to bypass the usual __init__ logic
+ # and just create a new instance with the required pointer.
+ inst = cls.__new__(cls)
+ inst._pointer = pointer
+ return inst
+
+
+
+
+
+ @classmethod
+ def _read(cls, buf):
+ ptr = buf.readU64()
+ if ptr == 0:
+ raise InternalError("Raw pointer value was null")
+ return cls._lift(ptr)
+
+ @classmethod
+ def _write(cls, value, buf):
+ if not isinstance(value, PartiallySignedBitcoinTransaction):
+ raise TypeError("Expected PartiallySignedBitcoinTransaction instance, {} found".format(value.__class__.__name__))
+ buf.writeU64(value._lower())
+
+ @classmethod
+ def _lift(cls, pointer):
+ return cls._make_instance_(pointer)
+
+ def _lower(self):
+ return self._pointer
+
+class SledDbConfiguration(ViaFfiUsingByteBuffer, object):
+ def __init__(self,path, tree_name ):
+ self.path = path
+ self.tree_name = tree_name
+
+ def __str__(self):
+ return "SledDbConfiguration(path={}, tree_name={} )".format(self.path, self.tree_name )
+
+ def __eq__(self, other):
+ if self.path != other.path:
+ return False
+ if self.tree_name != other.tree_name:
+ return False
+ return True
+
+ @staticmethod
+ def _read(buf):
+ return SledDbConfiguration(
+ path=FfiConverterString._read(buf),
+ tree_name=FfiConverterString._read(buf)
+ )
+
+ def _write(self, buf):
+ FfiConverterString._write(self.path, buf)
+ FfiConverterString._write(self.tree_name, buf)
+
+class TransactionDetails(ViaFfiUsingByteBuffer, object):
+ def __init__(self,fees, received, sent, txid ):
+ self.fees = fees
+ self.received = received
+ self.sent = sent
+ self.txid = txid
+
+ def __str__(self):
+ return "TransactionDetails(fees={}, received={}, sent={}, txid={} )".format(self.fees, self.received, self.sent, self.txid )
+
+ def __eq__(self, other):
+ if self.fees != other.fees:
+ return False
+ if self.received != other.received:
+ return False
+ if self.sent != other.sent:
+ return False
+ if self.txid != other.txid:
+ return False
+ return True
+
+ @staticmethod
+ def _read(buf):
+ return TransactionDetails(
+ fees=FfiConverterOptionalUInt64._read(buf),
+ received=FfiConverterUInt64._read(buf),
+ sent=FfiConverterUInt64._read(buf),
+ txid=FfiConverterString._read(buf)
+ )
+
+ def _write(self, buf):
+ FfiConverterOptionalUInt64._write(self.fees, buf)
+ FfiConverterUInt64._write(self.received, buf)
+ FfiConverterUInt64._write(self.sent, buf)
+ FfiConverterString._write(self.txid, buf)
+
+class Confirmation(ViaFfiUsingByteBuffer, object):
+ def __init__(self,height, timestamp ):
+ self.height = height
+ self.timestamp = timestamp
+
+ def __str__(self):
+ return "Confirmation(height={}, timestamp={} )".format(self.height, self.timestamp )
+
+ def __eq__(self, other):
+ if self.height != other.height:
+ return False
+ if self.timestamp != other.timestamp:
+ return False
+ return True
+
+ @staticmethod
+ def _read(buf):
+ return Confirmation(
+ height=FfiConverterUInt32._read(buf),
+ timestamp=FfiConverterUInt64._read(buf)
+ )
+
+ def _write(self, buf):
+ FfiConverterUInt32._write(self.height, buf)
+ FfiConverterUInt64._write(self.timestamp, buf)
+
+class ElectrumConfig(ViaFfiUsingByteBuffer, object):
+ def __init__(self,url, socks5, retry, timeout, stop_gap ):
+ self.url = url
+ self.socks5 = socks5
+ self.retry = retry
+ self.timeout = timeout
+ self.stop_gap = stop_gap
+
+ def __str__(self):
+ return "ElectrumConfig(url={}, socks5={}, retry={}, timeout={}, stop_gap={} )".format(self.url, self.socks5, self.retry, self.timeout, self.stop_gap )
+
+ def __eq__(self, other):
+ if self.url != other.url:
+ return False
+ if self.socks5 != other.socks5:
+ return False
+ if self.retry != other.retry:
+ return False
+ if self.timeout != other.timeout:
+ return False
+ if self.stop_gap != other.stop_gap:
+ return False
+ return True
+
+ @staticmethod
+ def _read(buf):
+ return ElectrumConfig(
+ url=FfiConverterString._read(buf),
+ socks5=FfiConverterOptionalString._read(buf),
+ retry=FfiConverterUInt8._read(buf),
+ timeout=FfiConverterOptionalUInt8._read(buf),
+ stop_gap=FfiConverterUInt64._read(buf)
+ )
+
+ def _write(self, buf):
+ FfiConverterString._write(self.url, buf)
+ FfiConverterOptionalString._write(self.socks5, buf)
+ FfiConverterUInt8._write(self.retry, buf)
+ FfiConverterOptionalUInt8._write(self.timeout, buf)
+ FfiConverterUInt64._write(self.stop_gap, buf)
+
+class EsploraConfig(ViaFfiUsingByteBuffer, object):
+ def __init__(self,base_url, proxy, timeout_read, timeout_write, stop_gap ):
+ self.base_url = base_url
+ self.proxy = proxy
+ self.timeout_read = timeout_read
+ self.timeout_write = timeout_write
+ self.stop_gap = stop_gap
+
+ def __str__(self):
+ return "EsploraConfig(base_url={}, proxy={}, timeout_read={}, timeout_write={}, stop_gap={} )".format(self.base_url, self.proxy, self.timeout_read, self.timeout_write, self.stop_gap )
+
+ def __eq__(self, other):
+ if self.base_url != other.base_url:
+ return False
+ if self.proxy != other.proxy:
+ return False
+ if self.timeout_read != other.timeout_read:
+ return False
+ if self.timeout_write != other.timeout_write:
+ return False
+ if self.stop_gap != other.stop_gap:
+ return False
+ return True
+
+ @staticmethod
+ def _read(buf):
+ return EsploraConfig(
+ base_url=FfiConverterString._read(buf),
+ proxy=FfiConverterOptionalString._read(buf),
+ timeout_read=FfiConverterUInt64._read(buf),
+ timeout_write=FfiConverterUInt64._read(buf),
+ stop_gap=FfiConverterUInt64._read(buf)
+ )
+
+ def _write(self, buf):
+ FfiConverterString._write(self.base_url, buf)
+ FfiConverterOptionalString._write(self.proxy, buf)
+ FfiConverterUInt64._write(self.timeout_read, buf)
+ FfiConverterUInt64._write(self.timeout_write, buf)
+ FfiConverterUInt64._write(self.stop_gap, buf)
+
+class ExtendedKeyInfo(ViaFfiUsingByteBuffer, object):
+ def __init__(self,mnemonic, xprv, fingerprint ):
+ self.mnemonic = mnemonic
+ self.xprv = xprv
+ self.fingerprint = fingerprint
+
+ def __str__(self):
+ return "ExtendedKeyInfo(mnemonic={}, xprv={}, fingerprint={} )".format(self.mnemonic, self.xprv, self.fingerprint )
+
+ def __eq__(self, other):
+ if self.mnemonic != other.mnemonic:
+ return False
+ if self.xprv != other.xprv:
+ return False
+ if self.fingerprint != other.fingerprint:
+ return False
+ return True
+
+ @staticmethod
+ def _read(buf):
+ return ExtendedKeyInfo(
+ mnemonic=FfiConverterString._read(buf),
+ xprv=FfiConverterString._read(buf),
+ fingerprint=FfiConverterString._read(buf)
+ )
+
+ def _write(self, buf):
+ FfiConverterString._write(self.mnemonic, buf)
+ FfiConverterString._write(self.xprv, buf)
+ FfiConverterString._write(self.fingerprint, buf)
+
+class BdkError(ViaFfiUsingByteBuffer):
+
+ # Each variant is a nested class of the error itself.
+ # It just carries a string error message, so no special implementation is necessary.
+ class InvalidU32Bytes(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(1)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Generic(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(2)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class ScriptDoesntHaveAddressForm(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(3)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class NoRecipients(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(4)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class NoUtxosSelected(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(5)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class OutputBelowDustLimit(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(6)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class InsufficientFunds(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(7)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class BnBTotalTriesExceeded(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(8)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class BnBNoExactMatch(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(9)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class UnknownUtxo(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(10)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class TransactionNotFound(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(11)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class TransactionConfirmed(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(12)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class IrreplaceableTransaction(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(13)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class FeeRateTooLow(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(14)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class FeeTooLow(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(15)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class FeeRateUnavailable(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(16)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class MissingKeyOrigin(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(17)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Key(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(18)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class ChecksumMismatch(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(19)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class SpendingPolicyRequired(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(20)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class InvalidPolicyPathError(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(21)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Signer(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(22)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class InvalidNetwork(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(23)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class InvalidProgressValue(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(24)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class ProgressUpdateError(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(25)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class InvalidOutpoint(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(26)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Descriptor(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(27)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class AddressValidator(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(28)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Encode(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(29)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Miniscript(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(30)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Bip32(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(31)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Secp256k1(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(32)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Json(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(33)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Hex(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(34)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Psbt(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(35)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class PsbtParse(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(36)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Electrum(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(37)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Esplora(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(38)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+ class Sled(ViaFfiUsingByteBuffer, Exception):
+ def _write(self, buf):
+ buf.writeI32(39)
+ message = str(self)
+ FfiConverterString._write(message, buf)
+
+ @classmethod
+ def _read(cls, buf):
+ variant = buf.readI32()
+ if variant == 1:
+ return cls.InvalidU32Bytes(FfiConverterString._read(buf))
+ if variant == 2:
+ return cls.Generic(FfiConverterString._read(buf))
+ if variant == 3:
+ return cls.ScriptDoesntHaveAddressForm(FfiConverterString._read(buf))
+ if variant == 4:
+ return cls.NoRecipients(FfiConverterString._read(buf))
+ if variant == 5:
+ return cls.NoUtxosSelected(FfiConverterString._read(buf))
+ if variant == 6:
+ return cls.OutputBelowDustLimit(FfiConverterString._read(buf))
+ if variant == 7:
+ return cls.InsufficientFunds(FfiConverterString._read(buf))
+ if variant == 8:
+ return cls.BnBTotalTriesExceeded(FfiConverterString._read(buf))
+ if variant == 9:
+ return cls.BnBNoExactMatch(FfiConverterString._read(buf))
+ if variant == 10:
+ return cls.UnknownUtxo(FfiConverterString._read(buf))
+ if variant == 11:
+ return cls.TransactionNotFound(FfiConverterString._read(buf))
+ if variant == 12:
+ return cls.TransactionConfirmed(FfiConverterString._read(buf))
+ if variant == 13:
+ return cls.IrreplaceableTransaction(FfiConverterString._read(buf))
+ if variant == 14:
+ return cls.FeeRateTooLow(FfiConverterString._read(buf))
+ if variant == 15:
+ return cls.FeeTooLow(FfiConverterString._read(buf))
+ if variant == 16:
+ return cls.FeeRateUnavailable(FfiConverterString._read(buf))
+ if variant == 17:
+ return cls.MissingKeyOrigin(FfiConverterString._read(buf))
+ if variant == 18:
+ return cls.Key(FfiConverterString._read(buf))
+ if variant == 19:
+ return cls.ChecksumMismatch(FfiConverterString._read(buf))
+ if variant == 20:
+ return cls.SpendingPolicyRequired(FfiConverterString._read(buf))
+ if variant == 21:
+ return cls.InvalidPolicyPathError(FfiConverterString._read(buf))
+ if variant == 22:
+ return cls.Signer(FfiConverterString._read(buf))
+ if variant == 23:
+ return cls.InvalidNetwork(FfiConverterString._read(buf))
+ if variant == 24:
+ return cls.InvalidProgressValue(FfiConverterString._read(buf))
+ if variant == 25:
+ return cls.ProgressUpdateError(FfiConverterString._read(buf))
+ if variant == 26:
+ return cls.InvalidOutpoint(FfiConverterString._read(buf))
+ if variant == 27:
+ return cls.Descriptor(FfiConverterString._read(buf))
+ if variant == 28:
+ return cls.AddressValidator(FfiConverterString._read(buf))
+ if variant == 29:
+ return cls.Encode(FfiConverterString._read(buf))
+ if variant == 30:
+ return cls.Miniscript(FfiConverterString._read(buf))
+ if variant == 31:
+ return cls.Bip32(FfiConverterString._read(buf))
+ if variant == 32:
+ return cls.Secp256k1(FfiConverterString._read(buf))
+ if variant == 33:
+ return cls.Json(FfiConverterString._read(buf))
+ if variant == 34:
+ return cls.Hex(FfiConverterString._read(buf))
+ if variant == 35:
+ return cls.Psbt(FfiConverterString._read(buf))
+ if variant == 36:
+ return cls.PsbtParse(FfiConverterString._read(buf))
+ if variant == 37:
+ return cls.Electrum(FfiConverterString._read(buf))
+ if variant == 38:
+ return cls.Esplora(FfiConverterString._read(buf))
+ if variant == 39:
+ return cls.Sled(FfiConverterString._read(buf))
+
+ raise InternalError("Raw enum value doesn't match any cases")
+
+
+# Declaration and FfiConverters for BdkProgress Callback Interface
+
+class BdkProgress:
+ def update(progress,message):
+ raise NotImplementedError
+
+
+
+def py_foreignCallbackCallbackInterfaceBdkProgress(handle, method, args, buf_ptr):
+
+ def invoke_update(python_callback, args):
+ rval = None
+ with args.consumeWithStream() as buf:
+ rval = python_callback.update(
+ FfiConverterFloat._read(buf),
+ FfiConverterOptionalString._read(buf)
+ )
+ return RustBuffer.alloc(0)
+ # TODO catch errors and report them back to Rust.
+ # https://github.com/mozilla/uniffi-rs/issues/351
+
+
+ cb = FfiConverterCallbackInterfaceBdkProgress._lift(handle)
+ if not cb:
+ raise InternalError("No callback in handlemap; this is a Uniffi bug")
+
+ if method == IDX_CALLBACK_FREE:
+ FfiConverterCallbackInterfaceBdkProgress.drop(handle)
+ # No return value.
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 0
+
+ if method == 1:
+ buf_ptr[0] = invoke_update(cb, args)
+ # Value written to out buffer.
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 1
+
+
+ # This should never happen, because an out of bounds method index won't
+ # ever be used. Once we can catch errors, we should return an InternalException.
+ # https://github.com/mozilla/uniffi-rs/issues/351
+
+ # An unexpected error happened.
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return -1
+
+# We need to keep this function reference alive:
+# if they get GC'd while in use then UniFFI internals could attempt to call a function
+# that is in freed memory.
+# That would be...uh...bad. Yeah, that's the word. Bad.
+foreignCallbackCallbackInterfaceBdkProgress = FOREIGN_CALLBACK_T(py_foreignCallbackCallbackInterfaceBdkProgress)
+
+# The FfiConverter which transforms the Callbacks in to Handles to pass to Rust.
+rust_call(lambda err: _UniFFILib.ffi_bdk_b9b3_BdkProgress_init_callback(foreignCallbackCallbackInterfaceBdkProgress, err))
+FfiConverterCallbackInterfaceBdkProgress = FfiConverterCallbackInterface(foreignCallbackCallbackInterfaceBdkProgress)
+class FfiConverterUInt8(Primitive):
+ @staticmethod
+ def _read(buf):
+ return FfiConverterUInt8._lift(buf.readU8())
+
+ @staticmethod
+ def _write(value, buf):
+ buf.writeU8(FfiConverterUInt8._lower(value))
+class FfiConverterUInt32(Primitive):
+ @staticmethod
+ def _read(buf):
+ return FfiConverterUInt32._lift(buf.readU32())
+
+ @staticmethod
+ def _write(value, buf):
+ buf.writeU32(FfiConverterUInt32._lower(value))
+class FfiConverterUInt64(Primitive):
+ @staticmethod
+ def _read(buf):
+ return FfiConverterUInt64._lift(buf.readU64())
+
+ @staticmethod
+ def _write(value, buf):
+ buf.writeU64(FfiConverterUInt64._lower(value))
+class FfiConverterFloat(Primitive):
+ @staticmethod
+ def _read(buf):
+ return FfiConverterFloat._lift(buf.readFloat())
+
+ @staticmethod
+ def _write(value, buf):
+ buf.writeFloat(FfiConverterFloat._lower(value))
+class FfiConverterString:
+ @staticmethod
+ def _read(buf):
+ size = buf.readI32()
+ if size < 0:
+ raise InternalError("Unexpected negative string length")
+ utf8Bytes = buf.read(size)
+ return utf8Bytes.decode("utf-8")
+
+ @staticmethod
+ def _write(value, buf):
+ utf8Bytes = value.encode("utf-8")
+ buf.writeI32(len(utf8Bytes))
+ buf.write(utf8Bytes)
+
+ @staticmethod
+ def _lift(buf):
+ with buf.consumeWithStream() as stream:
+ return stream.read(stream.remaining()).decode("utf-8")
+
+ @staticmethod
+ def _lower(value):
+ with RustBuffer.allocWithBuilder() as builder:
+ builder.write(value.encode("utf-8"))
+ return builder.finalize()
+# Helper code for OfflineWallet class is found in ObjectTemplate.py
+# Helper code for OnlineWallet class is found in ObjectTemplate.py
+# Helper code for PartiallySignedBitcoinTransaction class is found in ObjectTemplate.py
+# Helper code for Confirmation record is found in RecordTemplate.py
+# Helper code for ElectrumConfig record is found in RecordTemplate.py
+# Helper code for EsploraConfig record is found in RecordTemplate.py
+# Helper code for ExtendedKeyInfo record is found in RecordTemplate.py
+# Helper code for SledDbConfiguration record is found in RecordTemplate.py
+# Helper code for TransactionDetails record is found in RecordTemplate.py
+# Helper code for BlockchainConfig enum is found in EnumTemplate.py
+# Helper code for DatabaseConfig enum is found in EnumTemplate.py
+# Helper code for MnemonicType enum is found in EnumTemplate.py
+# Helper code for Network enum is found in EnumTemplate.py
+# Helper code for Transaction enum is found in EnumTemplate.py
+# Helper code for BdkError error is found in ErrorTemplate.py
+
+
+class FfiConverterOptionalUInt8(FfiConverterUsingByteBuffer):
+ @staticmethod
+ def _write(value, buf):
+ FfiConverterOptional._write(value, buf, lambda v, buf: FfiConverterUInt8._write(v, buf))
+
+ @staticmethod
+ def _read(buf):
+ return FfiConverterOptional._read(buf, lambda buf: FfiConverterUInt8._read(buf))
+
+
+class FfiConverterOptionalUInt32(FfiConverterUsingByteBuffer):
+ @staticmethod
+ def _write(value, buf):
+ FfiConverterOptional._write(value, buf, lambda v, buf: FfiConverterUInt32._write(v, buf))
+
+ @staticmethod
+ def _read(buf):
+ return FfiConverterOptional._read(buf, lambda buf: FfiConverterUInt32._read(buf))
+
+
+class FfiConverterOptionalUInt64(FfiConverterUsingByteBuffer):
+ @staticmethod
+ def _write(value, buf):
+ FfiConverterOptional._write(value, buf, lambda v, buf: FfiConverterUInt64._write(v, buf))
+
+ @staticmethod
+ def _read(buf):
+ return FfiConverterOptional._read(buf, lambda buf: FfiConverterUInt64._read(buf))
+
+
+class FfiConverterOptionalFloat(FfiConverterUsingByteBuffer):
+ @staticmethod
+ def _write(value, buf):
+ FfiConverterOptional._write(value, buf, lambda v, buf: FfiConverterFloat._write(v, buf))
+
+ @staticmethod
+ def _read(buf):
+ return FfiConverterOptional._read(buf, lambda buf: FfiConverterFloat._read(buf))
+
+
+class FfiConverterOptionalString(FfiConverterUsingByteBuffer):
+ @staticmethod
+ def _write(value, buf):
+ FfiConverterOptional._write(value, buf, lambda v, buf: FfiConverterString._write(v, buf))
+
+ @staticmethod
+ def _read(buf):
+ return FfiConverterOptional._read(buf, lambda buf: FfiConverterString._read(buf))
+
+
+class FfiConverterSequenceEnumTransaction(FfiConverterUsingByteBuffer):
+ @staticmethod
+ def _write(value, buf):
+ FfiConverterSequence._write(value, buf, lambda v, buf: v._write(buf))
+
+ @staticmethod
+ def _read(buf):
+ return FfiConverterSequence._read(buf, lambda buf: Transaction._read(buf))
+
+__all__ = [
+ "InternalError",
+ "Network",
+ "DatabaseConfig",
+ "Transaction",
+ "BlockchainConfig",
+ "MnemonicType",
+ "SledDbConfiguration",
+ "TransactionDetails",
+ "Confirmation",
+ "ElectrumConfig",
+ "EsploraConfig",
+ "ExtendedKeyInfo",
+ "generate_extended_key",
+ "restore_extended_key",
+ "OfflineWallet",
+ "OnlineWallet",
+ "PartiallySignedBitcoinTransaction",
+ "BdkError",
+ "BdkProgress",
+]
+
diff --git a/tests/test_bdk.py b/tests/test_bdk.py
new file mode 100644
index 0000000..85a6542
--- /dev/null
+++ b/tests/test_bdk.py
@@ -0,0 +1,33 @@
+import bdkpython as bdk
+
+
+# taken from bdk test suite @ https://github.com/bitcoindevkit/bdk/blob/master/src/descriptor/template.rs#L676
+descriptor = "wpkh(tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/84h/0h/0h/0/*)"
+config = bdk.DatabaseConfig.MEMORY("")
+client = bdk.BlockchainConfig.ELECTRUM(
+ bdk.ElectrumConfig(
+ "ssl://electrum.blockstream.info:60002",
+ None,
+ 5,
+ None,
+ 100
+ )
+)
+
+
+def test_address_BIP84_testnet():
+ wallet = bdk.OfflineWallet(descriptor, bdk.Network.TESTNET, config)
+ address = wallet.get_new_address()
+ assert address == "tb1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvvztyse"
+
+# def test_wallet_balance():
+# wallet = bdk.OnlineWallet(
+# descriptor=descriptor,
+# change_descriptor=descriptor,
+# network=bdk.Network.TESTNET,
+# database_config=config,
+# blockchain_config=client
+# )
+# wallet.sync()
+# balance = wallet.get_balance()
+# assert balance > 0
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..95f6045
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,10 @@
+[tox]
+envlist =
+ py39
+
+[testenv]
+deps =
+ pytest
+ bdk
+commands =
+ pytest --verbose --override-ini console_output_style=count