FAQ

Does the SDK ever hold private keys?

No. Walletd is the only component that ever sees a private key. The SDK is a typed HTTP client and nothing more.

Why not pydantic?

The wire payload is a dict. Wrapping it in a pydantic model would add a runtime construction cost, a 5 MB transitive dependency, and a forward-compat liability (strict-by-default models break when walletd adds a new field). TypedDict gives full mypy / pyright coverage at zero cost and forward-compats trivially.

If you specifically want a pydantic model, build it on top — pass the SDK's dict into MyModel.model_validate(result).

Why is there no retry on -32020?

Walletd already retries upstream node failures with linear backoff (default 4 attempts, 500 ms base — see walletd's RetryPolicy). Layering another retry inside the SDK would multiply latency without adding reliability.

If walletd itself is unreachable (you get TransportError, not UpstreamError), retry at the caller — that's a separate failure mode the SDK can't sensibly handle for you.

Can I use one client for both read and spend?

Yes. The SDK doesn't model scopes — a single Client carries one bearer token, and walletd enforces what that token can do. If your token is read-only and you call transfer, you get AuthenticationError.

If you want hard separation between deposit-watcher and withdrawal-worker, construct two Client instances with two tokens and let the type system make crossing the line obvious.

How do I detect "wallet is empty" vs "wallet is busy"?

InsufficientBalanceError.in_flight_reserved:

except InsufficientBalanceError as e:
    if e.in_flight_reserved:
        # transient — pending transfers will free UTXOs
        retry_later()
    else:
        # actually empty — fund the wallet
        alert()

Why does transfer use from_ instead of from?

from is a Python keyword and can't be used as a parameter name. The SDK accepts from_ (trailing underscore, a common Python convention) and translates to the wire field from before sending.

How do I run the integration tests?

The integration suite spawns a real walletd binary. Build walletd first:

cd ../exfer-walletd
cargo build --release
cd ../exfer-py
pytest -m integration

Or point at a custom location:

WALLETD_BINARY=/usr/local/bin/exfer-walletd pytest -m integration

Tests skip cleanly if the binary isn't available.

Which walletd versions are supported?

The SDK tracks walletd's current minor release. v0.3.0 of the SDK is tested against walletd v0.4.3; the CI integration job pins to that exact tag. Any newer walletd should work — unknown error codes gracefully fall through to bare WalletdError.

Where do I report bugs?

github.com/exfer-stack/exfer-py/issues. For walletd-side issues (wire format, error semantics), report to exfer-walletd instead.