Conversation
Made it consistent for both Desktop and Mobile
…tched
The where clause nested Op.or inside the postCompleteState JSON path
object, which Sequelize's query builder rejects with 'Invalid value
{ cleanupCompleted: false }'. The error was caught and only logged, so
post-processing of completed ramps silently never ran. Use the
documented dotted-path JSONB syntax with Op.or at the top level, and
add a DB-backed regression test that fails against the old query.
…ute comparison Adds an observational BlindPay payin FX quote (BRL -> USDT/USDB) alongside the existing SquidRouter/Avenia/Nabla route quotes in compareRoutesUpfront, to evaluate whether BlindPay is a cheaper source of stablecoin over time. - New apps/rebalancer/src/services/blindpay: FX-rate client + types - fetchBlindpayShadowQuoteUsdc returns a USDC-equivalent (6dp) quote, or null when unconfigured or below BlindPay's 5 BRL minimum - Shadow only: logged in the route comparison but never added to candidates, so it is never actually routed through - Optional BLINDPAY_* config; documented in .env.example
…orts waitUntilTrueWithTimeout raced its poll loop against a timeout without cancelling it, so every timed-out call leaked a loop polling forever (the production CPU-leak mechanism). The timeout now aborts the loop via an AbortController, and waitUntilTrue/checkEvmBalancePeriodically/ checkEvmNativeBalancePeriodically/checkEvmBalanceForToken accept an optional AbortSignal so callers can cancel abandoned waits. Error messages and BalanceCheckError semantics are unchanged.
The processor's 10-minute execution timeout used Promise.race, which abandons handler.execute without stopping it. Abandoned executions kept their polling loops running forever and piled up with every retry and recovery-worker pass until the CPU pegged (production incidents Jun 26 - Jul 8); a pegged event loop then slowed Postgres SCRAM handshakes enough for Supavisor to fail new connections with EAUTHTIMEOUT. Abandoned executions could also perform late side effects (e.g. Avenia ticket creation) concurrently with the live retry. The processor now aborts each timed-out execution via an AbortSignal threaded through PhaseHandler.execute/executePhase. MAX_EXECUTION_TIME_MS is env-overridable (PHASE_PROCESSOR_MAX_EXECUTION_TIME_MS) so the regression test doesn't wait 10 minutes. Spec updated accordingly.
The Avenia balance wait ran 30 minutes per execution, which always outlived the processor's 10-minute execution timeout - so the 30-minute payment-window cancellation (isPaymentTimeoutReached) never ran for recovered ramps and never-paid onramps churned indefinitely. The wait now runs in 5-minute chunks (like the EVM balance check) and surfaces each chunk timeout as a recoverable error, letting the wall-clock payment window cancel the ramp. Both waits also accept the processor's AbortSignal. Spec updated accordingly.
With pool min 0 / idle 10s every quiet period dropped all connections, so each request burst re-ran the full SCRAM handshake through the Supabase pooler (Supavisor), which fails with EAUTHTIMEOUT when the event loop is busy. Keep 2 warm connections and a 60s idle timeout.
…otification Threads the observational BlindPay shadow quote from compareRoutesUpfront through the preflight quotes and rebalance state into the Base completion Slack message, rendered as a 'BlindPay shadow (not routed)' line with the delta vs the executed route. Absent when BlindPay is unconfigured.
…rocess-query Repair postProcessCompletedStates JSONB query that never matched
…on gap Review feedback on #1257: register the abort listener and re-check signal.aborted afterwards — a listener added to an already-aborted signal never fires, so this ordering leaves no path where an abort is missed. Adds a test for the pre-aborted case.
…ation test Review feedback on #1257: a non-numeric PHASE_PROCESSOR_* env value parsed to NaN, and setTimeout(..., NaN) fires immediately — a misconfigured deploy would time out every phase instantly. Fall back to the defaults for non-finite or non-positive values (tested). The cancellation test now snapshots and restores the env overrides and the shadowed nablaSwap registry entry in afterAll, per the repo's restore convention.
Review feedback on #1257: fetch had no timeout, so a stalled BlindPay request could hang the Promise.allSettled upstream and stall the whole rebalance run — cap it at 30s via AbortSignal.timeout. Also adds client tests (configuration gating, request shape incl. the timeout signal, non-OK handling), which lifts the rebalancer function coverage back above the CI ratchet floor (49.57% -> 53.45%).
… api populateApi() returns the cached instance when one exists, but it was also used as the refresh mechanism in getApi() (on spec version change) and executeApiCall() (on bad-signature retry). In both cases the cached ApiPromise was returned unchanged, so the refresh was a no-op and the stale connection kept being used. Add refreshApi(), which disconnects the cached instance and replaces it with a fresh connection, and use it on the spec-version-change path, the forceRefresh path, and the bad-signature retry. The retry also rebuilds the extrinsic against the refreshed api, since the original call is bound to the stale registry. populateApi/populateAllApis stay idempotent for normal startup. Add tests covering populateApi idempotency and both refresh paths; the refresh tests fail against the previous implementation.
Change 'Buy & Sell' to 'Open App' in MobileMenu and DesktopNavbar
…hrows Review feedback on #1257: handler.execute was invoked eagerly inside the Promise.race array literal, so a handler that throws synchronously would propagate before .finally was attached — clearTimeout never ran and the orphaned timeout promise later rejected with nobody listening. Wrap the call in Promise.resolve().then(...) so sync throws become a rejection of the race and cleanup runs reliably. Regression test asserts no unhandledRejection fires after the timeout window.
Address review feedback on refreshApi: the stale instance was removed and disconnected before the replacement connection existed, so a failed reconnect left the network without any cached api. Create the new instance first, then swap and best-effort disconnect the stale one. Also cache new instances only after connect/isReady so concurrent readers never observe a still-connecting api. Add a test asserting a failed refresh keeps the previous instance cached and undisconnected.
…noop fix(shared): make ApiManager refresh paths actually replace the stale api
PR #1255 merged a mis-formatted translations file; every PR merge ref now fails the format check. Formatted with biome.
The i18n commit in #1255 landed mis-formatted, so 'bun verify' fails on staging and on every PR merge ref. Formatted with biome.
…ting style(frontend): fix en.json formatting breaking bun verify on staging
…se-polling Cancel abandoned phase executions (production CPU-leak root cause)
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
…-price-with-fastforex # Conflicts: # apps/api/src/api/services/priceFeed.service.test.ts # apps/api/src/api/services/priceFeed.service.ts # docs/security-spec/03-ramp-engine/quote-lifecycle.md # docs/security-spec/06-cross-chain/fund-routing.md
… messages and logs
Provider (Avenia/BRLA, Alfredpay) failures raised during POST /v1/ramp/register,
/update and /start previously bubbled up as a plain Error, got masked to an opaque
`{"code":500,"message":"Internal server error"}`, and left no trace of which provider
call failed. A recent incident (BRLA returning `{"error":"user is blocked"}` on
createPixInputTicket) surfaced both gaps: the caller saw only a 500 and retried
uselessly, and the logs did not say which Avenia call rejected the account.
- Add ProviderApiError base class (packages/shared/.../providerApiError.ts) carrying
provider/endpoint/method/status/responseBody. BrlaApiError and AlfredpayApiError now
extend it and are thrown from their services' sendRequest paths. The message shape is
kept identical (`Request failed with status '<code>'. Error: <body>`) so existing
consumers (BRLA controller handleApiError, Alfredpay controller substring guards) still
work.
- mapProviderFailure() in ramp.controller normalizes a ProviderApiError to 422 (upstream
4xx — account/request rejected) or 502 (upstream 5xx/transport — provider unavailable)
with a generic "payment provider" message; the raw upstream body is never forwarded to
the caller. The register/update/start catches log the failing provider/endpoint/method/
status (never query params, which may carry PII) and classify the event as
provider_error instead of internal_error.
- Remove a stray console.log in createPixInputTicket.
Tests: mapProviderFailure unit tests (Avenia 4xx/5xx, Alfredpay, passthroughs); BRLA
controller regression test locking the message-format invariant. Security spec
(07-operations/api-surface.md) updated.
The whole-module @vortexfi/shared stub was never restored, so under bun's process-wide module mocks it leaked into later test files. Files that resolve real token configs at import (the Alfredpay corridor scenarios) then saw EvmToken/evmTokenConfig as undefined and threw 'token config missing', which surfaced as import errors and 500s on ramp registration in CI. Local runs passed only because macOS file-discovery order was benign. Capture and restore the real modules in afterAll, matching every other shared-mocking test; also drop the vestigial ../../../index mock.
…-with-fastforex Replace nabla swap price with fastforex
…ailures, log body - Rename shared `ProviderApiError` -> `ProviderHttpError` (file providerHttpError.ts) to avoid colliding with the existing price-layer `ProviderApiError` in api/errors/providerErrors.ts. - Restore the prototype chain in the base error (`Object.setPrototypeOf(this, new.target.prototype)`, `name = this.constructor.name`) so `instanceof` used by mapProviderFailure stays reliable after bundling — matches repo convention. - Wrap `fetch()` in both BRLA and Alfredpay request paths: transport failures (DNS/timeout/connection reset) now throw the provider error with `status: 0`, so mapProviderFailure normalizes them to 502 instead of a generic 500. - Log a truncated (300-char) provider response body in the structured log context so the failure reason (e.g. "user is blocked") is captured server-side; never returned to the caller. Keeps the security-spec claim accurate. - Remove the duplicate `./squidrouter` export in the shared services barrel. Tests updated for the new `providerResponseBody` log field, plus cases for the status-0 transport mapping and body truncation. Security spec wording updated.
…rror note Address PR review nit: list POST /v1/ramp/update and /start with their full prefixes instead of the ambiguous /update, /start shorthand.
…error-logging Surface fiat-provider failures on ramp endpoints with clear messages and logs
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.
No description provided.