feat(ffi): Python bindings (UniFFI) - #228
Conversation
Adds a Python package for the ant-ffi UniFFI surface — the same compiled library that backs the Swift and Kotlin SDKs — giving Python the daemon-less, direct-network client (complementing antd-py, which needs a running antd). Generated with the in-crate uniffi-bindgen (no Rust changes); async Client methods surface as native asyncio `async def`. Contents (ffi/python/): - ant_ffi/__init__.py re-exports the generated surface; the generated module and native library are build outputs (gitignored), produced by build.sh - pyproject.toml / setup.py package metadata + platform-wheel tag machinery (py3-none-<platform>: one wheel per OS/arch, any Python 3) - tests/test_smoke.py offline: import + version + EVM address derivation - examples/upload_download_demo.py devnet upload/download round-trip - ffi/scripts/build.sh gains a Python binding-generation step alongside the C#/Kotlin/Swift steps Proven: the demo does a byte-identical file round-trip against a local devnet; the smoke tests pass offline. Cross-platform wheel building and PyPI publishing follow in a separate PR. Note: the distribution name `ant-ffi` is a deliberate placeholder — the import name `ant_ffi` is fixed by the UniFFI crate namespace regardless, so the final PyPI name is a one-line change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lockfile-only, semver-compatible bump that clears the cargo-audit failure on the ffi dependency tree. ruint <1.20.0 has incorrect overflow flags and truncated shift amounts in Uint shift operations (RUSTSEC-2026-0220); it comes in transitively via alloy-primitives. ant-ffi compiles unchanged and cargo audit passes (only the pre-existing allowed unmaintained warnings remain). Pre-existing on main — the advisory was published after the last green run, so it surfaced here because the audit job scans against the live advisory DB. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # ffi/rust/Cargo.lock
Working title matching the repo; import name stays ant_ffi (UniFFI namespace). If a product-level rename happens the PyPI project will be deleted and recreated under the new name (V2-880). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
Requesting changes for two release-artifact failure modes found at exact head 0c4ef5a16cc2e667864d653ec5522af8ee6bedb6.
1. A clean checkout silently builds an unusable wheel
ffi/python/setup.py never checks that the gitignored generated module and native library are present. From a clean copy of ffi/python I ran:
python -m build --wheel
The command succeeded, but the wheel contained only ant_ffi/__init__.py plus metadata. It installed successfully, then failed immediately:
ModuleNotFoundError: No module named 'ant_ffi.ant_ffi'
Please make the package build fail clearly unless ant_ffi/ant_ffi.py and the expected platform native library are staged (or generate them in the build), and add a built-wheel install/import check. The cross-platform release workflow may remain a follow-up; the important point here is not to emit a publishable-looking broken wheel.
2. The sdist embeds the build host's native library
With generated files staged, default python -m build created ant_sdk-0.0.8.tar.gz containing ant_ffi/libant_ffi.dylib. That source distribution is platform-neutral by filename but platform-specific in content, so rebuilding it on Linux or Windows can package the wrong binary.
Please either exclude native libraries from sdists/provide a valid source-build path, or explicitly reject sdist creation for this wheel-only distribution. The release workflow should upload only repaired platform wheels.
The happy path itself checks out: release Rust build and UniFFI generation passed; all 16 ant-ffi tests passed; staged wheels passed twine check, Python 3.9/3.13 smoke tests, installed-wheel import/version checks, and offline wallet derivation. GitHub's three checks are green. The blockers are the two silent/misleading artefact paths above.
…facts Review findings on this PR: a clean checkout silently produced a wheel that installs but cannot import (generated module + native lib are gitignored build outputs setup.py never checked for), and a default sdist embedded the build host's native library under a platform-neutral name. - bdist_wheel now refuses to run unless ant_ffi/ant_ffi.py and the host platform's native library are staged, with a pointer at the build scripts. - sdist is blocked outright: this is a wheel-only distribution. - ffi/scripts/check-python-wheel.sh installs a built wheel into a throwaway venv and imports it from outside the repo - the post-build proof the wheel works. README documents the wheel-only policy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dirvine
left a comment
There was a problem hiding this comment.
Previous findings: all resolved at 52b5924c0971a41b98673d843fda3550fd3e415c.
- Clean-checkout wheel builds now fail clearly unless the generated binding and expected native library are staged.
- Source distributions are explicitly rejected for this wheel-only package.
- The installed-wheel import/version checker passes in a fresh environment.
Reverified locally: missing-artifact and sdist failure paths; release build and UniFFI generation; staged wheel build; twine check; Python 3.9/3.13 installed-wheel imports; offline smoke tests; all 16 ant-ffi tests; formatting and diff checks. Current GitHub checks are green.
Independent review consensus found no remaining merge blockers. Minor release-workflow hardening can remain follow-up work.
What
Adds a Python package for the
ant-ffiUniFFI surface — the same compiled library that backs the Swift and Kotlin SDKs — giving Python the daemon-less, direct-network client. This complementsantd-py, which requires a runningantddaemon.Generated with the in-crate
uniffi-bindgen(no Rust changes toant-ffi). AsyncClientmethods surface as native asyncioasync def.Contents (
ffi/python/)ant_ffi/__init__.py— re-exports the generated surface. The generated module (ant_ffi.py) and the native library are build outputs (gitignored), produced bybuild.sh.pyproject.toml/setup.py— package metadata + platform-wheel tag machinery (py3-none-<platform>: one wheel per OS/arch, valid for any Python 3, since the bindings are pure ctypes over a bundled native lib).tests/test_smoke.py— offline: import + version + EVM address derivation (mirrorsffi/csharp/AntFfi.Tests).examples/upload_download_demo.py— devnet upload/download round-trip.ffi/scripts/build.sh— gains a Python binding-generation step alongside the existing C#/Kotlin/Swift steps.Proven
examples/upload_download_demo.pydoes a byte-identical file round-trip against a local devnet (paid upload, progress callbacks, sha256 match).tests/test_smoke.pypasses offline.Follow-up
Cross-platform wheel building (manylinux / macOS universal2 / Windows) and PyPI publishing via Trusted Publishing land in a separate PR to keep this focused on the bindings themselves.
Naming
Settled 2026-09-02: the distribution name is
ant-sdk(matches the repo; documented inpyproject.toml). The import nameant_ffiis fixed by the UniFFI crate namespace regardless of the PyPI name. If a product-level rename happens later, the PyPI project will be deleted and recreated under the new name (V2-880).🤖 Generated with Claude Code