Expose authenticated Esplora in the bindings#163
Open
philippem wants to merge 7 commits into
Open
Conversation
The async Esplora client returned the HTTP response for any non-retryable
status without checking it was successful, so callers parsed error bodies
as the expected success payload. An authenticated backend replying
`402 {"error":"Insufficient credits"}` therefore surfaced as a confusing
`invalid type: map, expected a sequence` JSON error instead of the actual
problem.
Fail fast in get_with_retry/post_with_retry on a non-success status,
returning the status code and a bounded prefix of the response body.
Expose the Esplora/Waterfalls authentication already available in lwk_wollet through the UniFFI bindings, so Python/Kotlin/Swift/C#/Go consumers can connect to enterprise backends. - Add a TokenProvider enum (None/Static/Blockstream OAuth2) and map it to lwk_wollet::clients::TokenProvider - Add headers and token_provider fields to EsploraClientBuilder and wire them through the From conversion - Export TokenProvider from the crate root - Add offline unit tests for the conversion and builder wiring
Add a Python tab to the "Authenticated Esplora" section of the clients book page, backed by a runnable example that builds OAuth2, static-token, and custom-header clients and asserts the builder wiring offline. When CLIENT_ID/CLIENT_SECRET are set it also performs a live authenticated request.
A non-trivial watch-only test that syncs a real mainnet wallet through an authenticated Blockstream Enterprise endpoint and asserts on-chain facts (tx history, per-asset balance == sum of UTXO values, confidential UTXOs). No private keys are needed; it exercises both the Esplora and Waterfalls code paths and checks they agree, plus a negative path asserting invalid credentials are rejected. Gated on CLIENT_ID/CLIENT_SECRET; defaults to the production enterprise endpoint, overridable via ESPLORA_BASE_URL / ESPLORA_LOGIN_URL.
Two runnable regtest examples: - watch_only_wallet.py: online watch-only wallet (descriptor only, no keys) paired with an offline signer; the watcher prepares and verifies an unsigned PSET, the signer signs, the watcher finalizes and broadcasts. - liquid_change.py: demonstrates confidential change handling, identifying the internal-chain change output, unblinding it, and showing change lands on a fresh address on each spend.
fetch_oauth_token went straight to .json() and looked for "access_token",
so a failed token request (e.g. HTTP 401 {"error":"invalid_client"} for
bad credentials) was reported as a generic "Missing access_token in
response" that hid the real cause. Check the response status first and
surface it via error_for_status, e.g.:
... /token returned HTTP 401 Unauthorized: {"error":"invalid_client",
"error_description":"Invalid client or Invalid client credentials"}
Caveat: this changes the error text on a rejected token request. Callers
that string-match the old "Missing access_token in response" message (only
emitted now when the endpoint returns 2xx without the field) may need to
update. No type or signature changes.
Register the watch-only and confidential-change examples plus the authenticated Esplora example with build_foreign_language_testcases! so they run under `cargo test -p lwk_bindings --features foreign_bindings`. The two regtest examples are hermetic (LwkTestEnv). The authenticated example only builds clients and skips its live check unless CLIENT_ID / CLIENT_SECRET are set, so it makes no external request in CI.
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.
Expose authenticated Esplora in the bindings
Motivation
lwk_wolletalready supports authenticated Esplora/Waterfalls backends (OAuth2, static token, custom headers), but the UniFFI bindings didn't expose any of it — so Python/Kotlin/Swift/C#/Go consumers couldn't connect to authenticated Blockstream Enterprise endpoints. While wiring this up, the work also surfaced a robustness gap: the Esplora client reported authenticated failures (e.g.402 Insufficient credits) as a misleadinginvalid type: map, expected a sequenceJSON error.What's in here
TokenProviderenum (None/Static/BlockstreamOAuth2) plusheadersandtoken_providerfields onEsploraClientBuilder, wired through to the core builder. Both fields are optional with defaults, so this is additive — existingEsploraClientBuilder(...)calls are unchanged.get_with_retry/post_with_retrynow fail fast on any non-2xx status, returning the status code and response body instead of letting callers misparse an error payload. Behavioral change to existing methods (tip,full_scan,broadcast), but only in the error path.Validation
TokenProviderconversion and builder wiring;fmt+clippyclean.elementsd+ Liquidelectrs.Reproducing the authenticated scan
Get OAuth2 API credentials from https://dashboard.blockstream.info/ (and ensure the account has credits), then:
Defaults target the production enterprise endpoint; override with
ESPLORA_BASE_URL/ESPLORA_LOGIN_URL(e.g. for UAT). WithoutCLIENT_ID/CLIENT_SECRETthe test logs a skip and exits 0.API impact
No breaking changes. New additive binding API (
TokenProvider+ two optional builder fields); the only behavioral change is the Esplora client now erroring clearly on non-2xx responses.