Skip to content

refactor(ibex): collapse payInvoice raw-fetch seam onto the SDK path - #479

Merged
islandbitcoin merged 1 commit into
mainfrom
refactor/collapse-payinvoice-seam
Aug 13, 2026
Merged

refactor(ibex): collapse payInvoice raw-fetch seam onto the SDK path#479
islandbitcoin merged 1 commit into
mainfrom
refactor/collapse-payinvoice-seam

Conversation

@islandbitcoin

Copy link
Copy Markdown
Contributor

What the seam was

payInvoice in src/services/ibex/client.ts invoked the generated SDK through Ibex.authentication.withAuth(() => Ibex.ibex.payInvoiceV2(...)) with a .catch(httpErrorHandler), instead of the standard Ibex.payInvoiceV2(...).then(errorHandler) shape every sibling method uses. The seam existed because ibex-client 3.2.0's ApiError wrapper discarded the response body (lnflash/ibex-client#6): the raw FetchError (parsed body on .data) had to reach httpErrorHandler intact or insufficient-balance 400s were unclassifiable.

Why it's safe to collapse now

ibex-client 3.3.0 (bumped in #477) makes ApiError extract the body itself — httpCode / ibexResponse / ibexMessage — and errorHandler classifies that shape through the standard path. The wrapper's contract (withAuth(...).catch(_ => new ApiError(_)), resolving auth failures as AuthenticationError values) means every error class the seam handled now arrives at errorHandler with the same information:

  • insufficient-balance 400s → InsufficientIbexBalance, ErrorLevel.Info, UUID-stripped client message, unstripped vendor detail preserved for logs/spans
  • unclassified IBEX errors → generic IbexError (Warn) carrying the IBEX body detail exactly once (no duplication with ApiError's own embedded copy), capped at MAX_IBEX_MESSAGE_LENGTH for oversized (e.g. Cloudflare HTML) bodies
  • auth failures → IbexError at ErrorLevel.Critical
  • network-level failures / non-Error throwables → generic IbexError without a status

What happened to httpErrorHandler

Removed. After the collapse it had zero production callers (ibexFetch/ibexPost build their own IbexError inline and never routed through it), and a fully-dead defense-in-depth handler rots. Its dedicated describe block is removed with it; every behavioral pin it carried was ported onto the errorHandler path (see below), so no assertion coverage was lost — only the duplicate route to it.

Tests

  • test/flash/unit/services/ibex/errors.spec.ts — the "ibex-client 3.3.0 integration" describe now pins the full collapsed-path behavior with real ApiError construction (the exact shape the wrapper resolves with): JSON and non-JSON insufficient-balance bodies, casing preservation, case-insensitive classification, payment-already-prepared, unclassified detail carried exactly once with status + level, bounded message on oversized bodies, network-level failure without status, non-Error throwable tolerance, status preservation on body-less errors.
  • test/flash/unit/services/ibex/pay-invoice.spec.ts — mock now mirrors ibex-client 3.3.0's wrapper contract (unwrap .data, resolve AuthenticationError, resolve thrown errors as new ApiError(err)) instead of stubbing withAuth; adds a payInvoice-level auth-failure test pinning ErrorLevel.Critical.
  • Targeted suite: 7 suites, 70 passed. Full yarn test:unit: 181 suites, 1585 passed / 3 skipped. yarn tsc-check clean; eslint clean on all changed files.

Fixes #478

🤖 Generated with Claude Code

https://claude.ai/code/session_015fAxSGEsL2LsMx1uCjAzHH

payInvoice called the generated SDK through Ibex.authentication.withAuth
directly so a failed payment's FetchError — carrying the parsed IBEX error
body on `.data` — reached httpErrorHandler intact. The seam existed because
ibex-client 3.2.0's ApiError discarded the response body
(lnflash/ibex-client#6), which made insufficient-balance 400s
unclassifiable through the standard path.

As of ibex-client 3.3.0 (#477), ApiError extracts the body itself
(httpCode / ibexResponse / ibexMessage) and errorHandler classifies it
through the standard path, so payInvoice now uses the same
`Ibex.payInvoiceV2(body).then(errorHandler)` shape as every sibling method.

httpErrorHandler had no remaining callers after the collapse and is removed
along with its dedicated tests; every behavioral pin it carried
(insufficient-balance classification with UUID-stripped client message and
preserved detail, single-copy detail on unclassified errors, capped
message on oversized bodies, status preservation, network-failure and
non-Error tolerance) now lives on the errorHandler path in errors.spec.ts,
constructed as real ApiErrors — the exact shape the collapsed path
produces. pay-invoice.spec.ts mocks ibex-client 3.3.0's wrapper contract
and gains a payInvoice-level auth-failure test pinning ErrorLevel.Critical.

Fixes #478

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015fAxSGEsL2LsMx1uCjAzHH
@islandbitcoin
islandbitcoin merged commit 132d6d5 into main Aug 13, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Collapse payInvoice raw-fetch seam now that ibex-client 3.3.0 ApiError carries the body

2 participants