Run the Monerium OAuth and white-label apps in parallel on the EUR onramp - #1372
Merged
Merged
Conversation
Plan for running the Monerium OAuth and white-label applications in parallel on the EUR corridor, stacked on the Monerium reintegration. Records the Phase 0 sandbox probe results: link-at-login is no longer supported, the user token can link wallets and request the single per-profile IBAN, and the white-label app cannot see OAuth-onboarded profiles.
MoneriumApiService.forUserAccessToken builds a client that acts as an end user of the Monerium OAuth app with the same transport, schemas, redaction, and timeouts as the white-label singleton. It never requests a client token and lets a 401 surface so the caller can require reauthentication.
EUR onramp registration now resolves the bound profile through the white-label app first and, when that app answers 403 or 404 for it, through the user's backend-held OAuth token. The white-label app cannot see profiles that only authorized the OAuth app (sandbox probe P1), so this is the only read path for OAuth-onboarded users. A missing binding fails with MONERIUM_ONBOARDING_REQUIRED and a missing or rejected OAuth session with MONERIUM_REAUTHENTICATION_REQUIRED; the serving app is logged, never persisted.
The widget will run the same backend-driven OAuth flow as the dashboard, so the start request names its client and the backend picks the exact callback from the configured allowlist (MONERIUM_REDIRECT_URI, MONERIUM_WIDGET_REDIRECT_URI) and binds it into the OAuth transaction. Caller-supplied redirect URIs stay impossible; a mismatch with Monerium's registered URIs renders an empty authorization page, so the widget flow is refused outright until its callback is configured.
POST /v1/monerium/wallet verifies the owner's EOA signature over Monerium's fixed ownership message and the EOA requirement server-side, links the address through whichever app can read the profile, and requests the profile's single IBAN when none exists. POST /v1/monerium/iban/move changes the IBAN destination only on an explicit owner request to an already-linked address, because it redirects future SEPA deposits. GET /v1/monerium/status and the onboarding aggregate report EUR ramp readiness from the same list reads registration uses, without mutating provider state; a persisted approval stays readable when the OAuth session is gone.
The shared Monerium machine now carries the EUR ramp readiness the backend reports on the status endpoint, and the API client gains the wallet link, IBAN move, and OAuth client selector calls so the dashboard and the widget share one Monerium integration.
EU onboarding runs through Monerium OAuth again. An approved EU sender gets a second step on the corridor card: connect the wallet they will pay in with, sign Monerium's ownership message (no gas), and let Vortex request or move the profile's IBAN to it. The onramp form offers EUR once that wallet is linked and connected, sends it as the ramp's wallet address, and the transfer machine signs the wallet-owned permit before showing the SEPA instructions, which the backend releases only after that signature.
A client that can only open the Monerium authorization in another tab (the embedded widget) needs a way to re-read the status once the user returns, so Redirecting accepts REFRESH.
EUR verification in the widget now runs the shared Monerium OAuth machine behind the existing OTP login. Monerium returns to the registered /widget callback; the persisted ramp hands the code and state to the restored verification step, which completes the exchange, and then a second step links the connected EVM wallet by signing Monerium's ownership message and waits for the profile's IBAN to point to it, asking before moving an IBAN that sits elsewhere. EUR pay-ins register the connected wallet as the ramp's wallet address so the existing user-signing actor collects the owner permit before the SEPA instructions appear. The Mykobo child stays only for persisted legacy flows.
EurHandler (formerly MykoboHandler) registers EUR/SEPA BUY with the destination and the user's Monerium-linked wallet, signs only the ephemeral-owned transactions, and VortexSdk.registerRamp returns the wallet's ERC-2612 permit in unsignedTransactions for submitUserTransactions. MONERIUM_ONBOARDING_REQUIRED and MONERIUM_REAUTHENTICATION_REQUIRED map to dedicated errors. The SDK never supported the Monerium onramp before, so replacing the Mykobo onramp's email/ipAddress fields with walletAddress breaks no live integration; the legacy Mykobo SELL adapter stays for persisted flows.
The API pages and the integration skill described EUR BUY as direct-API only; it now runs through the SDK once the user has onboarded with Monerium and linked the paying wallet in the dashboard or widget.
Convert the accepted proposal into adr-0006, describe both Monerium applications as active profile sources in the operations interface and security spec, flip the audit checklist items the first-party clients now satisfy, and record the backend-memory OAuth session dependency as RISK-025.
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
ebma
added this pull request to stack #1373
September 15, 2026 07:36
✅ Deploy Preview for vortexfi canceled.
|
✅ Deploy Preview for vrtx-dashboard canceled.
|
…ote error The Monerium flow mints on Polygon and bridges onward, so a Polygon (or non-EVM) destination has no flow. Clients used to receive an opaque "No block flow mapped" 400; expose the rule as a shared helper and a public QuoteError so pickers and error mapping can share it.
The onramp form and quote explorer offered (and defaulted to) Polygon for EUR pay-ins, which the API rejects.
… row Both providers map to the EU corridor and merged by status rank alone, so a pre-Monerium user's approved Mykobo row could shadow the Monerium row and its wallet readiness, asking for a wallet link forever. deriveOnboardings moves into a pure mappers module so it can be unit-tested without the transfer actor's localStorage bootstrap.
…ow closing while redirecting GET /v1/monerium/status answers 200 with rampError for a persisted approval whose backend OAuth session is gone, so the approved branch swallowed the reconnect need and the widget looped between wallet linking and the quote. Redirecting also ignored CLOSE, which the widget's cancel button sends.
…escapable The wallet flow polled provisioning forever behind a spinner with no cancel; the redirect panel had no way to open Monerium when a popup was blocked, and a reconnect after session loss showed first-verification copy.
EUR pay-ins were mapped to the retired Mykobo sequence, so five of the Monerium phases had no index and the ring sat at 0% or jumped.
Registration and readiness resolved the binding on the active entity only, while OAuth and status calls are typed. A business-active profile that onboarded EUR through the widget (always individual) failed registration with MONERIUM_ONBOARDING_REQUIRED despite approval, and the onboarding status loop swallowed the 403 silently.
…wner with one in flight Permits for one owner are prepared against the same ERC-2612 nonce and the mint executor attributes settlement by balance delta, so two live ramps could deliver one SEPA credit to the other ramp's destination and strand the loser on a consumed nonce. Moving the IBAN mid-ramp redirects the mint the executor is polling for. Both are cheap 409s at the boundary.
A 24-hour permit that starts at registration expires on a routine standard SEPA transfer (Friday to Monday). The spender is the ramp's ephemeral and the value exact, so aligning with the one-week swap presign widens time only.
…allowance The executor recorded the invalidation and then threw a recoverable error for a condition that can never resolve, so the processor and recovery worker retried it forever. Reconciliation is the right label; the worker's re-drive of paused ramps is a pre-existing system-wide property left as is.
A revoked or expired refresh token answered every later call with a generic 502 until restart, so clients never saw the reconnect prompt the spec promises. Also log the POST /ibans 400 that is assumed to mean 'already requested'.
…0.9.0 error names A walletAddress that is not the Monerium-linked owner filtered the owner permit away and returned an empty transaction list, leaving the ramp unstartable with no hint. The three Monerium* error classes exported by 0.9.0 were removed without aliases; keep them as deprecated aliases.
…ed SDK and widget The vortex-integration skill said in one place that the SDK supports EUR BUY and in two others that it does not. The widget callback sample pointed at port 5473 instead of the frontend's 5173, and the Monerium ops doc never named the env vars it depends on.
The EUR kill-switch was removed on the condition that a fake-world corridor scenario exists; this adds it. A fake Monerium white-label app joins the fake world, FakeEvm learns bytecode probes, simulateContract, ERC-2612 reads, deterministic raw-tx hashes, and strict receipts, and the scenario drives quote -> register -> update -> the real processor to completion, plus the consumed-nonce reconciliation pause and the registration guards.
Every field of the fixed EURe/USDC approval and swap now has a negative case, all six executor failure branches are exercised (deadline, missing input, allowance mismatch before and after, soft and hard minimum), the unconfigured-issue-fee 503 is asserted, and the Monerium service test restores the config it mutates.
…s collected on-chain
Bind each EUR action to the selected legal profile and serialize ramp registration with IBAN destination changes so a live pay-in cannot be redirected. Require an informed confirmation before changing a provisioned IBAN destination, and keep unexpected OAuth and wallet failures visible to Sentry.
Contributor
There was a problem hiding this comment.
Note
Copilot was unable to run its full agentic suite in this review.
Pull request overview
Enables the renewed EUR/SEPA (Monerium) onramp end-to-end across API, SDK, Dashboard, and Widget by supporting both Monerium “white-label” and OAuth profile sources, adding wallet/IBAN readiness + linking flows, and returning the owner permit as a user-owned signature step.
Changes:
- Add dual-app Monerium identity resolution (white-label first, OAuth fallback) plus OAuth session reauthentication handling and wallet/IBAN readiness APIs.
- Update SDK + clients to treat the Monerium owner permit as a user-owned typed-data transaction (submit via
submitUserTransactions/ signature helpers), and add EUR-specific validation & errors. - Add destination-network constraints for EUR onramp (no Polygon), plus updated docs/specs and expanded fake-world + tests.
Reviewed changes
Copilot reviewed 113 out of 114 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/services/monerium/moneriumApiService.ts | Add user-token client mode + disable token retry for OAuth user clients |
| packages/shared/src/services/monerium/moneriumApiService.test.ts | Test user-token client behavior (no client-token request, no 401 retry) |
| packages/shared/src/helpers/networks.ts | Add doesNetworkSupportEurOnramp helper (exclude Polygon) |
| packages/shared/src/endpoints/ramp.endpoints.ts | Add customerType to ramp register request additionalData |
| packages/shared/src/endpoints/quote.endpoints.ts | Add EurOnrampNetworkUnsupported quote error |
| packages/sdk/test/vortexSdk.eurOnramp.test.ts | Add SDK tests for EUR onramp owner permit routing |
| packages/sdk/test/eurHandler.test.ts | Add EUR handler tests (linked wallet + customerType pass-through) |
| packages/sdk/test/errors.test.ts | Test new Monerium error mappings + deprecated aliases |
| packages/sdk/src/types.ts | Update EUR additionalData shape (walletAddress & customerType; deprecate email/ipAddress) |
| packages/sdk/src/handlers/MykoboHandler.ts | Replace Mykobo BUY with Monerium EUR handler behavior (permit expectations) |
| packages/sdk/src/errors.ts | Add EUR/Monerium onramp errors + parseAPIError mappings + aliases |
| packages/sdk/src/VortexSdk.ts | Return Monerium owner permit as user-owned tx; tighten signer matching |
| packages/sdk/README.md | Document EUR BUY SDK flow (walletAddress + permit submission) |
| packages/sdk/ARCHITECTURE.md | Update architecture notes to include new EUR handler behavior |
| packages/kyc/src/monerium/types.ts | Add OAuth client selector + readiness/wallet link types + richer status |
| packages/kyc/src/monerium/service.ts | Add wallet link / IBAN move endpoints + optional OAuth client selector |
| packages/kyc/src/monerium/service.test.ts | Test OAuth client selection + readiness route posting + auth-required mapping |
| packages/kyc/src/monerium/machine.ts | Add OAuth-client support, readiness fields, refresh + error reporting hooks |
| packages/kyc/src/monerium/machine.test.ts | Add tests for refresh, reauth status routing, and error reporting |
| packages/kyc/src/monerium/api.ts | Extend API interfaces with wallet readiness operations and client selector |
| packages/kyc/src/index.ts | Export readiness + wallet-link types and reauthentication code |
| docs/security-spec/RISK-REGISTER.md | Update Monerium risks: permit horizon + new OAuth-token operational risk |
| docs/security-spec/README.md | Update Monerium module description to reflect dual app + readiness |
| docs/security-spec/03-ramp-engine/ramp-phase-flows.md | Document dual identity source for EUR onramp registration |
| docs/product-dashboard.md | Document new Monerium OAuth + wallet link + IBAN move UX for EUR |
| docs/operations-testing.md | Update coverage matrix/notes to include Monerium corridor scenario + SDK coverage |
| docs/operations-monerium-interface.md | Update operations boundary: dual app, wallet link, IBAN move, config |
| docs/architecture-identity-model.md | Note dual-app binding behavior under single provider_customers binding |
| docs/api/pages/09-fiat-corridors.md | Update EUR corridor docs for SDK support + customerType + wallet permit flow |
| docs/api/pages/04-ramp-lifecycle.md | Update lifecycle docs: EUR BUY permit via SDK user-owned txs |
| docs/api/pages/02-quick-start-with-the-sdk.md | Update quickstart to include EUR BUY SDK flow |
| docs/api/pages/01-overview.md | Update overview: SDK returns Monerium permit as a user-owned tx |
| docs/api/openapi/vortex.openapi.json | Add Monerium ramp readiness fields to OpenAPI |
| docs/api/openapi/vortex.openapi.d.ts | Add Monerium ramp readiness typing to OpenAPI d.ts |
| docs/adr-0006-monerium-dual-app.md | Add ADR describing dual-app Monerium strategy and consequences |
| docs/README.md | Link ADR 0006 and update Monerium ops doc blurb |
| .agents/skills/vortex-integration/SKILL.md | Update integration skill doc for EUR SDK support + permit horizon |
| apps/frontend/src/types/searchParams.ts | Add Monerium OAuth callback query params parsing (code/state, error) |
| apps/frontend/src/translations/pt.json | Add Monerium KYC + wallet-link flow UI strings (PT) |
| apps/frontend/src/translations/en.json | Add Monerium KYC + wallet-link flow UI strings (EN) |
| apps/frontend/src/stores/quote/useQuoteStore.ts | Map new EUR onramp network unsupported error to friendly key |
| apps/frontend/src/services/api/api-client.ts | Route Monerium API paths to KYC Sentry domain |
| apps/frontend/src/pages/widget/index.tsx | Render Monerium KYC and wallet-link flow components in widget |
| apps/frontend/src/pages/progress/phaseFlows.ts | Add Monerium EUR ramp phase flow + getRampFlow helper |
| apps/frontend/src/pages/progress/phaseFlows.test.ts | Test Monerium phase sequence indexing for progress UI |
| apps/frontend/src/pages/progress/index.tsx | Use shared getRampFlow helper from phaseFlows |
| apps/frontend/src/machines/types.ts | Add Monerium callback + actor refs/types in ramp machine types |
| apps/frontend/src/machines/ramp.machine.ts | Register Monerium KYC + wallet child machines in ramp machine |
| apps/frontend/src/machines/ramp.machine.test.ts | Update KYC routing tests to Monerium KYC + wallet flow |
| apps/frontend/src/machines/ramp.context.ts | Initialize moneriumCallback in ramp context |
| apps/frontend/src/machines/moneriumWallet.machine.test.ts | Add Monerium wallet machine tests |
| apps/frontend/src/machines/moneriumKyc.machine.ts | Wire Monerium KYC machine with widget OAuth client + Sentry reporting |
| apps/frontend/src/machines/kyc.states.ts | Route EUR KYC to Monerium + add wallet-link step and callback refresh events |
| apps/frontend/src/machines/actors/registerAdditionalData.ts | Register EUR onramp with walletAddress + customerType (no email) |
| apps/frontend/src/machines/actors/register.actor.test.ts | Test EUR onramp register additionalData mapping |
| apps/frontend/src/hooks/useRampUrlParams.ts | Parse Monerium OAuth callback params and dispatch into persisted ramp |
| apps/frontend/src/contexts/rampState.tsx | Persist Monerium child states and expose Monerium selectors/actors |
| apps/frontend/src/constants/kybRegions.ts | Clarify Europe exclusion rationale (wallet + IBAN provisioning requirement) |
| apps/frontend/src/components/TokenSelection/TokenSelectionList/helpers.tsx | Filter token destinations for EUR BUY (exclude Polygon) |
| apps/frontend/src/components/Monerium/MoneriumWalletFlow.tsx | Add wallet-link UX (link/move/wait/failure) for Monerium |
| apps/frontend/src/components/Monerium/MoneriumKycFlow.tsx | Add Monerium OAuth KYC UX with reconnect/refresh support |
| apps/dashboard/src/services/transactions/userSigning.ts | Add Monerium wallet-link message signing helper |
| apps/dashboard/src/services/api/onboarding.service.ts | Add Monerium readiness (ramp) to onboarding DTO |
| apps/dashboard/src/services/api/onboarding.mappers.ts | Prefer Monerium row over legacy Mykobo on status ties and surface readiness |
| apps/dashboard/src/services/api/onboarding.mappers.test.ts | Test mapper tie-breaking and readiness surfacing |
| apps/dashboard/src/machines/transfer.machine.ts | Ensure onramp signs user-owned txs before showing payment instructions |
| apps/dashboard/src/machines/transfer.machine.test.ts | Test EUR onramp permit signing before payment instructions |
| apps/dashboard/src/machines/transfer.actors.ts | Return wallet-owned txs for both BUY and SELL (permit handling) |
| apps/dashboard/src/hooks/useApprovedCorridors.test.ts | Update onboarding DTO test fixture to include ramp |
| apps/dashboard/src/hooks/useActiveAccount.ts | Move onboarding derivation to shared mapper |
| apps/dashboard/src/domain/types.ts | Add optional Monerium readiness field to domain Onboarding type |
| apps/dashboard/src/domain/onramp.ts | Enable EU corridor onramp + readiness-based blockers + exclude Polygon network option |
| apps/dashboard/src/domain/onramp.test.ts | Test EU network filtering and readiness blockers |
| apps/dashboard/src/domain/corridors.ts | Re-enable EU onboarding (no corridors disabled) |
| apps/dashboard/src/domain/corridors.test.ts | Update corridor-disabled test expectations |
| apps/dashboard/src/components/transfer/OnrampPaymentInstructions.tsx | Add EUR/IBAN payment instruction rendering |
| apps/dashboard/src/components/transfer/OnrampForm.tsx | Add EUR-specific validation, readiness blocking UI, and network filtering |
| apps/dashboard/src/components/quote/QuoteExplorer.tsx | Filter networks for EUR BUY quotes (exclude Polygon) |
| apps/dashboard/src/components/onboarding/monerium/walletStep.ts | Add helper to decide link/move/ready states for Monerium wallet step |
| apps/dashboard/src/components/onboarding/monerium/MoneriumWalletLinkFlow.test.ts | Test Monerium wallet step decision helper |
| apps/dashboard/src/components/onboarding/OnboardingWizard.tsx | Add Monerium wallet-link step after approved Monerium onboarding |
| apps/dashboard/src/components/onboarding/CorridorCard.tsx | Add “link wallet” action state for approved Monerium onboarding |
| apps/api/src/tests/monerium-binding-entities.integration.test.ts | Integration tests for binding selection across individual/business entities |
| apps/api/src/tests/monerium-active-ramp.integration.test.ts | Integration tests for ramp/IBAN-move serialization via advisory locks |
| apps/api/src/test-utils/fake-world/index.ts | Install FakeMonerium into the fake world |
| apps/api/src/test-utils/fake-world/fake-monerium.ts | Implement fake Monerium client for tests (profiles/addresses/ibans) |
| apps/api/src/test-utils/fake-world/fake-evm.ts | Add deterministic raw-tx hashing, strict receipts, bytecode, simulateContract support |
| apps/api/src/config/vars.ts | Add MONERIUM_WIDGET_REDIRECT_URI config |
| apps/api/src/api/services/quote/index.ts | Publicly reject unsupported EUR onramp destination networks |
| apps/api/src/api/services/quote/eur-onramp-network.test.ts | Test EUR onramp destination rejection behavior |
| apps/api/src/api/services/phases/blocks/phases/monerium-self-transfer/transactions.ts | Align permit deadline with swap presign horizon |
| apps/api/src/api/services/phases/blocks/phases/monerium-self-transfer/execution.ts | Fail fast to reconciliation-required when permit is invalid and no allowance remains |
| apps/api/src/api/services/phases/blocks/phases/monerium-issue/registration.ts | Use resolved Monerium identity source + locks + enforce no concurrent live ramp per owner |
| apps/api/src/api/services/phases/blocks/flows/catalog.ts | Gate Eur onramp destinations using doesNetworkSupportEurOnramp |
| apps/api/src/api/services/phases/blocks/tests/monerium-self-transfer.test.ts | Add tests for deadline horizon and reconciliation-required behavior |
| apps/api/src/api/services/phases/blocks/tests/monerium-onramp-polygon-cross-chain.flow.test.ts | Test public 503 when Monerium issue fee unconfigured |
| apps/api/src/api/services/phases/blocks/tests/monerium-issue.registration.test.ts | Update registration tests for identity resolution + locks + active ramp guard |
| apps/api/src/api/services/monerium/monerium.service.ts | Add OAuth client allowlist, stronger refresh-token failure handling, readiness behavior |
| apps/api/src/api/services/monerium/monerium.service.test.ts | Add tests for refresh grant eviction, access token revocation, widget redirect allowlist |
| apps/api/src/api/services/monerium/identity.ts | Add identity resolver (white-label → oauth fallback) and binding selection rules |
| apps/api/src/api/services/monerium/identity.test.ts | Test identity resolver and reauthentication mapping |
| apps/api/src/api/services/monerium/active-ramp.ts | Add advisory locks + active-ramp detection by Monerium owner |
| apps/api/src/api/routes/v1/monerium.route.ts | Add wallet link and IBAN move routes |
| apps/api/src/api/controllers/onboarding.controller.ts | Attach Monerium readiness to onboarding status responses |
| apps/api/src/api/controllers/monerium.controller.ts | Add readiness handling + wallet/IBAN endpoints + OAuth client selector parsing |
| apps/api/.env.example | Document MONERIUM_WIDGET_REDIRECT_URI |
| docs/security-spec/RISK-REGISTER.md | Update Monerium risk entries and add OAuth-token operational risk |
| docs/README.md | Document ADR 0006 and update Monerium interface doc description |
Suppressed comments (4)
packages/sdk/src/VortexSdk.ts:1
- The PR updates the EUR handler implementation inside
packages/sdk/src/handlers/MykoboHandler.ts(now exportingEurHandler), butVortexSdkimports./handlers/EurHandler.js. Unless there is a corresponding file rename/addition not shown here, this will break the SDK build at runtime. Fix by either renaming the handler file toEurHandler.ts(and ensuring the emitted path isEurHandler.js) or by updating the import to point at the actual module that exportsEurHandler.
apps/dashboard/src/components/onboarding/OnboardingWizard.tsx:1 - This routes any
approvedMonerium onboarding toMoneriumWalletLinkFloweven when the account actually needs reauthentication (e.g., OAuth session lost, surfaced viareauthenticationRequired). In that case the wallet-link step typically cannot succeed and the UI should prompt a reconnect instead. Consider branching soapproved && reauthenticationRequiredrenders the Monerium KYC flow (reconnect copy), and onlyapproved && !reauthenticationRequiredgoes to wallet linking.
docs/api/pages/09-fiat-corridors.md:1 - This page still states the Monerium owner permit expires 24 hours after preparation, but other updated docs/spec in this PR (e.g. risk register / skill doc / self-transfer deadline change) indicate the horizon was extended ("one week" / swap deadline). Update this line to match the actual configured deadline window to avoid integrators building incorrect retry/expiry behavior.
packages/shared/src/services/monerium/moneriumApiService.ts:1 forUserAccessTokenaccepts any string, including an empty/whitespace token, which would silently sendAuthorization: Bearerand then surface as confusing upstream failures. Add a small invariant here (e.g., trim + reject empty) so callers get an immediate, actionable error when they accidentally pass a missing token.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+102
to
+117
| export async function linkWallet(req: Request, res: Response, next: NextFunction): Promise<void> { | ||
| try { | ||
| const user = authenticatedUser(req); | ||
| const body = (req.body ?? {}) as Record<string, unknown>; | ||
| res.status(httpStatus.OK).json( | ||
| await linkMoneriumWallet(user.userId, { | ||
| address: body.address, | ||
| chain: body.chain, | ||
| customerType: optionalCustomerType(body.customerType), | ||
| signature: body.signature | ||
| }) | ||
| ); | ||
| } catch (error) { | ||
| next(error); | ||
| } | ||
| } |
Comment on lines
+48
to
+50
| // An approved Monerium profile still needs the pay-in wallet linked and the IBAN pointed at it. | ||
| const walletLinkRequired = | ||
| corridor.provider === "monerium" && onboarding?.status === "approved" && onboarding.ramp?.iban !== "provisioned"; |
Comment on lines
91
to
+94
| {onboarding?.status === "rejected" ? ( | ||
| <p className="text-destructive text-xs">Verification was rejected — retry below or contact support.</p> | ||
| ) : walletLinkRequired ? ( | ||
| <p className="text-muted-foreground text-xs">Link the wallet you will pay in with to finish EUR setup.</p> |
A lost OAuth session returns no readiness, so the corridor card asked to link a wallet while its button asked to reconnect.
Polygon destinations settle from the fee-settled Uniswap output directly (USDC) or after one same-chain Squid swap, skipping the bridge, pay, and final-settlement phases.
Reverts the Polygon hiding from 6132997 now that the API serves it; the widget filter still drops non-EVM networks.
…s thin The pinned Uniswap v3 EURe/USDC pool runs out of in-range liquidity near 960 EUR; a 10,000 EUR quote would swallow 90% of the input.
Without them every profile, address, and IBAN read uses the user's OAuth token, so the EUR corridor can run before Monerium issues production white-label access.
The direct EURe/USDC 0.05% pool holds about 1k USDC in range and stops delivering near 1,000 EUR. EURe/USDC.e (0.30%) holds about 20k and USDC.e/USDC (0.01%) is deep, so the pinned route becomes a two-hop exactInput path: 5,000 EUR quotes at ~0.3% impact instead of failing.
2 tasks
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.
Stacked on #1359 (
monerium-reintegration). Merge that one first.What this does
Runs the Monerium OAuth application and the white-label application in parallel on the EUR onramp. At registration the backend reads the user's
monerium/eurbinding through the white-label app first and, when that app answers 403/404, through the user's backend-held OAuth token. Users who are onboarded to neither are offered OAuth onboarding in the dashboard and the widget, then link the wallet they will pay in with; Vortex links the EOA and requests (or, on explicit request, moves) the profile's single IBAN with the user's token. The on-chain flow (owner permit, EURe mint, Polygon swap, Squid settlement) is unchanged.Decision record:
docs/adr-0006-monerium-dual-app.md.Changes by layer
MoneriumApiService.forUserAccessToken()returns a user-token client with the same read/link/IBAN surface as the client-credentials singleton.services/monerium/identity.ts) used by EUR registration;POST /v1/monerium/wallet(signature-verified EOA link + IBAN request) andPOST /v1/monerium/iban/move;rampreadiness onGET /v1/monerium/statusand on the Monerium account ofGET /v1/onboarding/status; OAuth start takes aclientselector that picks the dashboard or widget redirect URI from config (MONERIUM_WIDGET_REDIRECT_URI); new error typesMONERIUM_ONBOARDING_REQUIREDandMONERIUM_REAUTHENTICATION_REQUIREDon registration.EurHandler(renamed fromMykoboHandler) registers the EUR onramp;registerRampreturns the owner permit as a user-owned transaction; new EUR error classes.Review fixes (2026-09-15)
A deep review of the combined stack (
monerium-dual-app-review.md, findings F1 to F19) was applied as one commit per concern on top of989e34287:apps/api/src/tests/corridors/eur-onramp-monerium.scenario.test.ts. Quote, registration, and presign submission go through the HTTP API; the real PhaseProcessor drives all eleven phases tocompleteagainst the fake world. The fake world gains a Monerium white-label double (fake-monerium.ts) and FakeEvm learns bytecode probes,simulateContract, ERC-2612 reads, deterministic raw-tx hashes, and opt-in strict receipts. Also covers the consumed-nonce reconciliation pause and the registration guards. This is the coverage the removed kill-switch comment required.QuoteError.EurOnrampNetworkUnsupported; the rule lives indoesNetworkSupportEurOnramp(shared) and the catalog, dashboard, and widget pickers all use it.rampError.code = MONERIUM_REAUTHENTICATION_REQUIRED) back toReady, so the widget offers a reconnect instead of looping between wallet linking and the quote;RedirectingacceptsCLOSEand shows a plain link for popup-blocked tabs.findActiveMoneriumRampForOwnerrefuses (409) a second live ramp for the same owner at registration and an IBAN move while a ramp waits on the IBAN's current wallet.MONERIUM_REAUTHENTICATION_REQUIRED; thePOST /ibans400 that is assumed to mean "already requested" is logged; the widget's IBAN wait is bounded and cancellable.EurOnrampErrorwhenwalletAddressis not the Monerium-linked owner (previously returned no permit); the threeMonerium*error names exported by 0.9.0 are kept as deprecated aliases.Security spec updated alongside each behavior change (invariants 14, 16, 20, 21; RISK-023 and RISK-024 wording; refresh-token threat row). Not addressed: F6 (issue-fee semantics, see Open items) and the P3 nits.
Sandbox probes (2026-09-14)
GET /addresses/{address}is readable cross-app.POST /addresses(201) andPOST /ibans(202); one IBAN per profile.Open items
MONERIUM_ISSUE_FEE_EURis installed as an anchor fee and the flow assumes Monerium deducts it at mint, but Monerium mints SEPA credits 1:1, so a non-zero value would be shown to the user yet never collected (it would stay in the owner's wallet). Decision: the fee is0today and stays0; the variable is kept for the future, and the ops doc says why it must not be raised before the fee is collected on-chain (full-input self-transfer plus on-chain distribution). Production startup still requires the variable to be set explicitly.Verification
bun run typecheck,bun run verify,bun run wire-contract:check,bun run docs:api:checkbun test(1898 pass, 54 skip, 0 fail) with a per-session test DBbun lintandbun typecheck