Skip to content

feat(stellar,test): Horizon error mapping, SEP-10 integration coverage, and multisig/settlement audit event pinning (#809, #810, #813) - #858

Open
adiainit wants to merge 4 commits into
mergepay:mainfrom
adiainit:build
Open

adiainit wants to merge 4 commits into
mergepay:mainfrom
adiainit:build

Conversation

@adiainit

@adiainit adiainit commented Oct 20, 2026 •

Copy link
Copy Markdown

Closes #809
Closes #810
Closes #813
Closes #812

Summary

This PR closes three issues with one shared theme — hardening the Stellar-facing
paths with structured errors, deeper authentication test coverage, and complete
audit-trail verification.

#810 — Stellar Horizon error mapping utility

  • New src/services/horizon-errors.ts: mapHorizonError() parses Stellar SDK /
    Horizon rejection shapes (response.data.extras.result_codes and base64
    result_xdr), reduces them to an allow-listed vocabulary of validated
    result-code tokens, and maps common transaction- and operation-level codes
    (tx_bad_seq, tx_insufficient_fee, op_underfunded, op_no_trust,
    op_low_reserve, …) onto stable API codes, appropriate HTTP statuses (409 /
    422 / 502), and actionable client hints. Unrecognized codes still answer
    PROVIDER_REJECTED, but only the validated tokens survive — raw upstream
    payloads, operation details, and SDK message text never reach the client.
  • src/services/timeout.ts — toProviderError() routes Horizon errors through
    the mapping so every rejection carries its code-specific status, code, and
    details. src/lib/provider-error.ts gains optional status / errorCode /
    details parameters; rate-limit and transport behaviour is unchanged.
  • src/services/stellar.ts — submitToHorizon() replaces the old
    string-interpolated upstream message with the shared mapping, keeping
    submission failures categorized for classifyJobFailure.
  • Tests: new tests/horizon-errors.test.ts (17 cases — result_xdr precedence,
    payload-leak rejection, de-duplication, TransportError unwrapping,
    rate-limit preservation) and tests/provider-errors.test.ts updated to the
    new mapped codes.

#813 — SEP-10 challenge and verification integration tests

  • tests/integration/sep10-auth.test.ts extended with the rejection paths a
    wallet can actually hit, each asserted to fail before the user store is
    touched:
    • a correctly signed challenge bound to a different web_auth_domain,
    • an unsigned challenge carrying only the server signature,
    • a home_domain request claim that does not match the request,
    • an unparseable envelope.
  • These sit alongside the existing cases that already pin the full cycle:
    challenge structure (domains, ops, nonce, time bounds, server signature),
    valid signature → JWT + refresh token with claims verification, wrong-key /
    wrong-domain / expired-challenge rejection (CHALLENGE_EXPIRED), and
    single-use redemption (replay → 401).
  • Verified with npm run test:integration against a real Postgres:
    14/14 files, 108 tests passed, including all 11 SEP-10 auth tests.

#809 — Audit logging for treasury multisig and group settlement

The helper (src/lib/audit-logger.ts, src/services/audit.ts), the handler
integration (treasury proposal create/sign/submit/fail; settlement
create/confirm/execute/retry/validation-failed), and secret redaction were
already in place; what was untested was the operator-facing structured Pino
event
for those two domains — the lines log aggregators filter on.

  • New tests/audit-treasury-settlement-events.test.ts pins full context —
    actor, action, target, groupId, outcome, timestamp — on both the durable
    audit row and the structured event = "audit" line for:
    • multisig signature collection (success) and submission failure, and
    • settlement execution (success) and envelope-validation failure.
  • The failure cases additionally prove that private keys, signed XDRs, and
    bearer tokens are replaced with [REDACTED] before reaching either sink.

Housekeeping

  • tests/request-timeout.test.ts: the explicit 2000 ms budget is runner
    patience only (the assertions pin the server's 80 ms behaviour), but it
    flaked under full-suite parallel load; widened to 10 s.

Testing & validation

  • npm run build — passes (strict TypeScript).
  • npm run lint — 0 errors (32 pre-existing warnings in untouched files).
  • npm test — 4271+ / 4295 tests green; every file touched by this PR passes,
    and the handful of full-suite failures observed on a heavily loaded dev
    machine are pre-existing 5 s wall-clock flakes that pass in isolation and
    passed in the pre-change baseline.
  • npm run test:integration — 14/14 files, 108 tests green against a real
    Postgres.

…rgepay#810)

Adds src/services/horizon-errors.ts to parse SDK/Horizon rejections (result_codes and result_xdr), reduce them to allow-listed tokens, and map common transaction and operation codes onto stable API codes, HTTP statuses, and actionable hints. toProviderError and submitToHorizon now route rejections through the mapping; unknown codes keep PROVIDER_REJECTED while carrying only validated tokens. Covered by tests/horizon-errors.test.ts (17 cases) and updated tests/provider-errors.test.ts.
…mergepay#813)

Extends tests/integration/sep10-auth.test.ts with the rejection paths a wallet can actually hit: a correctly signed challenge bound to a different web auth domain, an unsigned challenge carrying only the server signature, a home_domain claim that does not match the request, and an unparseable envelope — each asserted to fail before the user store is touched, alongside the existing valid-signature-to-JWT, wrong-key, wrong-domain, expired-challenge, and single-use redemption cases.
…flows (mergepay#809)

The durable audit rows for treasury multisig and group settlement actions were already asserted, but the operator-facing structured Pino lines (src/lib/audit-logger.ts) were only covered for group flows. tests/audit-treasury-settlement-events.test.ts now pins full context — actor, action, target, groupId, outcome, timestamp — on both the structured line and the durable row for multisig signature collection and submission failure and for settlement execution and envelope-validation failure, and proves private keys, signed XDRs, and tokens are redacted before reaching either sink.
The suite's explicit 2000ms budget is runner patience only — the assertions pin the server's 80ms behaviour — but under full-suite parallel load the wall clock stretches past it and the test flakes. Raised to 10s; the behaviour under test is still bounded by TIMEOUT_MS.
@drips-wave

drips-wave Bot commented Oct 20, 2026

Copy link
Copy Markdown

@adiainit Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants