fix(api): value offramp discount input in USD before applying inverted oracle rate#1275
Conversation
…d oracle rate The offramp expected-output math multiplies the request input amount by the inverted FIAT-USD oracle rate, assuming the input is USD-denominated. On the EVM offramp routes the input can be BRLA or EURC: a 1000 BRLA -> PIX offramp was treated as 1000 USD, inflating expectedOutput ~5x (and with it the maxSubsidy cap), over-paying the subsidy on every such quote. EURC -> SEPA offramps were symmetrically under-subsidized to zero. Value the input in USD first: USD-like stables pass through, fiat-pegged stables (BRLA, EURC) are valued at their peg's oracle rate, and other tokens fall back to the bridged USDC amount. Applied to both OffRampDiscountEngine and OffRampAlfredpayDiscountEngine, with a quote note whenever the valuation differs from the raw input amount.
✅ Deploy Preview for vrtx-dashboard canceled.
|
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
This PR fixes a denomination bug in the API quote discount engines for offramps where request.inputAmount was implicitly treated as USD, causing over-subsidy on BRLA→PIX and under-subsidy on EURC→SEPA. It introduces a USD-valuation step for offramp inputs before applying the inverted FIAT-USD oracle rate, and updates the security spec accordingly.
Changes:
- Add
getUsdDenominatedInputAmountto value offramp input amounts in USD (USD-like passthrough, fiat-pegged stable valuation via FIAT-USD rate, fallback to bridged USDC amount when present). - Wire the USD-valuation into both
OffRampDiscountEngineandOffRampAlfredpayDiscountEngine, including quote notes when valuation differs from raw input. - Add regression tests for the helper and update the discount mechanism security spec + invariants.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/security-spec/03-ramp-engine/discount-mechanism.md | Documents the USD-valuation step for offramp expectedOutput and adds an invariant to prevent misdenomination. |
| apps/api/src/api/services/quote/engines/discount/offramp.ts | Values offramp input in USD before computing expectedOutput with an inverted oracle rate; emits an explanatory note. |
| apps/api/src/api/services/quote/engines/discount/offramp-alfredpay.ts | Applies the same USD-valuation fix to the Alfredpay offramp discount engine; emits an explanatory note. |
| apps/api/src/api/services/quote/engines/discount/helpers.ts | Adds getUsdDenominatedInputAmount and stablecoin/peg mappings used by offramp discount engines. |
| apps/api/src/api/services/quote/engines/discount/helpers.test.ts | Adds tests and a regression case covering the BRLA misdenomination behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…allback Address CI failure and PR review comments: - Update the SELL 100 BRLA → BRL pricing golden from 500.00 to 100.00 BRL. The old value encoded the pre-fix bug (100 BRLA valued as 100 USD → 500 BRL); 100 BRLA is worth ~100 BRL at the 1:1 peg. Only outputAmount changed. - getUsdDenominatedInputAmount no longer throws on a FIAT-USD rate-feed failure for a pegged stable; it falls back to the bridged USDC amount (or raw input), so a transient price-feed outage cannot fail the quote from discount math. - Add EURC valuation + under-subsidy regression tests and rate-failure fallback tests to the helper suite. - Reflect the graceful fallback in the discount-mechanism security spec.
|
Pushed 5fece49 addressing the CI failure and both review comments. CI (golden test). The failing Comment 1 (rate-fetch can fail the quote). Good catch — Comment 2 (no EURC coverage). Added an explicit EURC valuation test plus an EURC→SEPA under-subsidy regression test (mirror of the BRLA case: raw 1000 EURC would undershoot to ~862 EUR, silently zeroing the subsidy). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
apps/api/src/api/services/quote/engines/discount/offramp.ts:64
- OffRampDiscountEngine is used for both PIX and SEPA-EVM routes (see offramp-to-sepa-evm.strategy.ts), but the anchor-fee variable name and log/quote-note strings still hard-code “BRL”. This makes quote notes and logs misleading for non-BRL offramps (e.g. EURC→SEPA) even though the math is currency-agnostic.
// Account for the anchor fee deducted in the Finalize stage, which reduces the user's received amount.
// We need to add it back to the expected output to calculate the subsidy correctly.
const anchorFeeInBrl = ctx.fees?.displayFiat?.anchor ? new Big(ctx.fees.displayFiat.anchor) : new Big(0);
Problem
The offramp discount engine computes
expectedOutput = inputAmount × (1 / oraclePrice), which assumesrequest.inputAmountis USD-denominated. That held for the legacy USDC offramps, but the EVM offramp routes accept BRLA and EURC as input:expectedOutputto ~5127 BRL instead of ~999 BRL. Since the subsidy cap ismaxSubsidy × expectedOutput, the cap was inflated ~5× too — the observed quote paid a 23.07 BRL subsidy where ~4.5 BRL was intended. This is a real over-subsidy leak on every BRLA→PIX offramp.Onramps are unaffected (their input is fiat, matching the non-inverted rate).
Fix
New
getUsdDenominatedInputAmounthelper values the request input in USD before the inverted-rate conversion:oraclePrice, so the conversion cancels exactly and expected output ≈ input × (1 + discount).evmToEvm.outputAmountDecimal) when available.Wired into both
OffRampDiscountEngineandOffRampAlfredpayDiscountEngine(which had the identical assumption and accepts the same input tokens). Both emit a quote note whenever the USD valuation differs from the raw input amount, so quote metadata makes the denomination visible.Tests
helpers.test.ts, including one reproducing the observed quote's numbers (1000 BRLA at rate 0.194757 must target ~1000 BRL, not ~5134).Security spec
docs/security-spec/03-ramp-engine/discount-mechanism.mdupdated: expected-output formula description now documents the USD-valuation step, and a new invariant #13 covers it.Verification after deploy
Request a BRLA→PIX quote on staging: the
subsidyblock should showexpectedOutputAmountDecimal≈ input amount (not ~5×), and the notes should include the newvalued input … USDline.