feat(ffi): Node.js bindings (napi-rs) - #231
Draft
Nic-dorman wants to merge 4 commits into
Draft
Conversation
Add ffi/node, a hand-written napi-rs wrapper exposing the ant-ffi direct-network client to Node.js and Electron. UniFFI has no maintained Node backend, so this mirrors the same surface with #[napi] wrappers that delegate to ant-ffi's plain `lib` target — no business logic in this crate. - Full surface: 9 connect* constructors, chunk/data/file/estimate/download methods, data-map helpers, wallet, and the complete external-signer flow (prepare/paymentTransactions/finalize/finalizeMerkle/cancel), plus the network_info/wait_for_receipt/merkle_winner_pool_hash free fns. - Progress: *_with_progress methods bridge a JS callback to ant-ffi's ProgressListener via a napi ThreadsafeFunction (background-thread safe). - async fns return Promises on napi's tokio runtime; Buffer/Uint8Array zero-copy; amounts stay decimal strings; auto-generated index.d.ts. - Packaging: @withautonomi/ant-ffi (scoped, distinct from the daemon SDK's `antd`), napi8 (Node 16+), 8-target platform matrix declared. - Offline smoke tests (node:test) + a live-devnet round-trip demo. Also re-exports ant-ffi's free functions at its crate root so downstream Rust consumers (this crate) can call them — previously only wired into the UniFFI scaffolding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ci-node.yml: offline smoke tests (native build) on Linux/macOS/Windows × Node 18/20/22, plus the full 8-target cross-compile matrix (mac x64/arm64, win x64/arm64, linux x64/arm64 × gnu+musl). Runs on PRs touching ffi/node or ffi/rust. gnu via --use-napi-cross (portable glibc 2.17 sysroot), musl via Zig + cargo-zigbuild. - publish-node.yml: builds the matrix and publishes @withautonomi/ant-ffi plus its per-platform packages to npm in lockstep (napi prepublish + npm publish, provenance/OIDC + NPM_TOKEN). Triggers on a node-v* tag or manual dispatch; inert until the npm org + token are set up (see the workflow header). Validated locally: `napi create-npm-dirs` generates all 8 platform packages with correct name/cpu/os/libc; `napi artifacts --output-dir` enforces the all-targets-present lockstep before assembly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First CI run surfaced setup issues (not compile failures — mac builds and the Node-22 native builds passed): - Windows build "jobs" failed only on a `ls -la` diagnostic step (PowerShell has no `-la`); the build itself succeeded. Dropped the step. - Tests failed on Node 18/20: @napi-rs/cli needs Node >= 20.12 (util.styleText) and `node --test "<glob>"` only expands globs on Node 21+. Fix: test matrix is Node 20/22, and the test script is bare `node --test` (filename-pattern discovery, ignores examples/). - linux-gnu --use-napi-cross failed: a macOS-generated package-lock omits the linux @napi-rs/cross-toolchain optional dep, so `npm ci` skipped it. Use `npm install` so npm resolves the runner-platform optional binaries. - linux-musl: replaced the flaky setup-zig action (404/502 mirror) with a deterministic `pip install ziglang` that cargo-zigbuild picks up. Also corrected the publish workflow's setup header: @WithAutonomi already exists on npm (holds the prior-network client) — the prerequisite is v2 publish access or a fresh scope, not creating the org. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
--use-napi-cross keeps hitting the npm optional-dependency bug (npm/cli#4828) on CI even with `npm install` — the cross-toolchain native binding isn't found. The musl targets already build fine via cargo-zigbuild (-x), and that path cross-compiles the full ant-core native tree (incl. secp256k1/blst C deps) cleanly. So route all four Linux targets through Zig instead. Leaves the six non-Linux targets and all tests unchanged (already green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
ffi/node— a Node.js / Electron binding for the daemon-less Autonomiclient, the sibling of the Python effort. UniFFI has no maintained Node backend,
so this is a hand-written napi-rs wrapper:
#[napi]types thatdelegate to
ant-ffi's plainlibtarget. No business logic lives in this crate —it mirrors the same inner client the Swift/Kotlin/C#/Python bindings wrap.
Published as
@withautonomi/ant-ffi(scoped, distinct from the daemon SDK'santdpackage). Install pulls a prebuilt per-platform.node— no Rust toolchain,no node-gyp.
Surface
connect*constructors (local / default / default+wallet /default-external-signer / custom peers ± wallet / devnet-manifest ±
external-signer / external-signer), all with optional Android
dataDircost estimate (+progress), streaming downloads-to-file (+progress), data-map
helpers,
walletApprove, and the full external-signer flow(
prepare*→paymentTransactions→finalizeUpload/finalizeUploadMerkle/
cancelUpload)fromPrivateKey/address/balanceOf*) + free fnsantFfiVersion,networkInfo,waitForReceipt,merkleWinnerPoolHashindex.d.ts(TS autocomplete out of the box)How it works
async fn→Promise<T>on napi's tokio runtime; the event loop never blocks_with_progressmethods bridge a plain JS(progress) => voidcallback toant-ffi'sProgressListenervia a napiThreadsafeFunction(safe from thecore's background progress task)
Buffer/Uint8Arrayzero-copy for payloads; token/gas amounts stay decimalstrings (they exceed 2^53)
Also re-exports
ant-ffi's free functions at its crate root so this crate can callthem (previously only wired into the UniFFI scaffolding).
Testing
index.d.tsgenerates correctlynode:test): import, version, offline EVM-addressderivation,
networkInfo, full-surface presence, free-fn rejectionfile upload with 10 progress callbacks across Encrypting→Quoting→Storing;
file download byte-identical. Validates the async + progress-bridge + payment
chain against ant-core 0.5.1 with zero protocol skew.
Not in this PR (follow-ups)
per-platform npm packages +
napi prepublish(publish needs an npm org/token).codeper variant; PartialUpload'smoney-visible fields on the JS error).
Draft pending the CI/distribution follow-up and review.
🤖 Generated with Claude Code