Skip to content

Run the Monerium OAuth and white-label apps in parallel on the EUR onramp - #1372

Merged
ebma merged 42 commits into
monerium-reintegrationfrom
monerium-dual-app
Sep 17, 2026
Merged

ebma merged 42 commits into
monerium-reintegrationfrom
monerium-dual-app

Conversation

@ebma

@ebma ebma commented Sep 15, 2026

Copy link
Copy Markdown
Member

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/eur binding 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

  • shared: MoneriumApiService.forUserAccessToken() returns a user-token client with the same read/link/IBAN surface as the client-credentials singleton.
  • api: identity resolver (services/monerium/identity.ts) used by EUR registration; POST /v1/monerium/wallet (signature-verified EOA link + IBAN request) and POST /v1/monerium/iban/move; ramp readiness on GET /v1/monerium/status and on the Monerium account of GET /v1/onboarding/status; OAuth start takes a client selector that picks the dashboard or widget redirect URI from config (MONERIUM_WIDGET_REDIRECT_URI); new error types MONERIUM_ONBOARDING_REQUIRED and MONERIUM_REAUTHENTICATION_REQUIRED on registration.
  • kyc: readiness types, wallet-link and IBAN-move API, machine stores readiness and can re-check a redirect.
  • dashboard: EU corridor enabled for onramps; wallet-link flow in the onboarding wizard and corridor card; onramp form blocks until the connected wallet is the linked one; owner permit signed before SEPA instructions are shown.
  • frontend (widget): Monerium KYC child machine with OAuth redirect/callback handling (top-level redirect, or new tab plus refresh when embedded), wallet-link child machine, EN/PT copy.
  • sdk: EurHandler (renamed from MykoboHandler) registers the EUR onramp; registerRamp returns the owner permit as a user-owned transaction; new EUR error classes.
  • docs: API pages, wire-contract snapshot, operations interface, security spec (invariants 16, 21 to 23, threats, checklist), RISK-025, ADR 0006.

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 of 989e34287:

  • F1 (blocker): hermetic EUR corridor scenario, 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 to complete against 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.
  • F2: Polygon (and non-EVM) EUR destinations are rejected at quote time with the public QuoteError.EurOnrampNetworkUnsupported; the rule lives in doesNetworkSupportEurOnramp (shared) and the catalog, dashboard, and widget pickers all use it.
  • F3 / F14: the kyc machine routes a persisted approval whose backend OAuth session is gone (rampError.code = MONERIUM_REAUTHENTICATION_REQUIRED) back to Ready, so the widget offers a reconnect instead of looping between wallet linking and the quote; Redirecting accepts CLOSE and shows a plain link for popup-blocked tabs.
  • F4 / F5: findActiveMoneriumRampForOwner refuses (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.
  • F7 / F8: the owner permit carries the swap presign deadline (one week) instead of 24 hours; an expired or consumed permit with no remaining allowance pauses the ramp for reconciliation instead of retrying forever.
  • F9: the Monerium binding is resolved across every entity the profile owns (active first), so a business-active profile that onboarded EUR through the widget can register; the onboarding status loop logs swallowed refresh errors.
  • F10 / F11: a rejected refresh grant evicts the cached credential and surfaces MONERIUM_REAUTHENTICATION_REQUIRED; the POST /ibans 400 that is assumed to mean "already requested" is logged; the widget's IBAN wait is bounded and cancellable.
  • F12 / F13: the progress ring tracks the Monerium phase sequence; the dashboard prefers an approved Monerium row over a legacy Mykobo row.
  • F15 / F16: the SDK throws EurOnrampError when walletAddress is not the Monerium-linked owner (previously returned no permit); the three Monerium* error names exported by 0.9.0 are kept as deprecated aliases.
  • F17 / F19: integration skill contradictions, widget callback port, and the Monerium env table in the ops doc.
  • F18: Uniswap route validation has a negative case per field, all six swap executor failure branches are exercised, the unconfigured-issue-fee 503 is asserted.

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)

  • White-label app gets 403 on profile, address, and IBAN reads for OAuth-only profiles; only GET /addresses/{address} is readable cross-app.
  • User token can POST /addresses (201) and POST /ibans (202); one IBAN per profile.
  • Legacy link-at-login authorize parameters are ignored by Monerium.
  • Redirect-URI mismatch renders an empty authorization page.

Open items

  • Issue-fee semantics (review F6). MONERIUM_ISSUE_FEE_EUR is 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 is 0 today and stays 0; 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.
  • The flow is pinned to Polygon mainnet while the sandbox mints on testnets, so the pay-in itself can only be verified in production. Onboarding, wallet linking, and IBAN provisioning were verified in the sandbox.
  • OAuth tokens stay in backend memory only (RISK-025). A restart forces a reconnect before the next EUR ramp; an encrypted refresh-token store is the upgrade path.
  • OAuth-to-white-label migration and external profile import remain undefined.
  • The dormant Mykobo widget form is left in place; removal is a separate cleanup.

Verification

  • bun run typecheck, bun run verify, bun run wire-contract:check, bun run docs:api:check
  • api: bun test (1898 pass, 54 skip, 0 fail) with a per-session test DB
  • dashboard (155), frontend (183), kyc (85), sdk (71), shared (174) suites; sdk bun lint and bun typecheck

ebma added 12 commits September 14, 2026 16:51
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.
@netlify

netlify Bot commented Sep 15, 2026

Copy link
Copy Markdown

Deploy Preview for vortex-sandbox ready!

Name Link
🔨 Latest commit 4477dee
🔍 Latest deploy log https://app.netlify.com/projects/vortex-sandbox/deploys/6aab9c3ee149fc0008f4658c
😎 Deploy Preview https://deploy-preview-1372--vortex-sandbox.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@ebma
ebma added this pull request to stack #1373 September 15, 2026 07:36
@netlify

netlify Bot commented Sep 15, 2026

Copy link
Copy Markdown

Deploy Preview for vortexfi canceled.

Name Link
🔨 Latest commit 4477dee
🔍 Latest deploy log https://app.netlify.com/projects/vortexfi/deploys/6aab9c3e55b98800088f0613

@netlify

netlify Bot commented Sep 15, 2026

Copy link
Copy Markdown

Deploy Preview for vrtx-dashboard canceled.

Name Link
🔨 Latest commit 4477dee
🔍 Latest deploy log https://app.netlify.com/projects/vrtx-dashboard/deploys/6aab9c3e4c879100078e2e2b

ebma added 14 commits September 15, 2026 11:44
…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.
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 exporting EurHandler), but VortexSdk imports ./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 to EurHandler.ts (and ensuring the emitted path is EurHandler.js) or by updating the import to point at the actual module that exports EurHandler.
    apps/dashboard/src/components/onboarding/OnboardingWizard.tsx:1
  • This routes any approved Monerium onboarding to MoneriumWalletLinkFlow even when the account actually needs reauthentication (e.g., OAuth session lost, surfaced via reauthenticationRequired). In that case the wallet-link step typically cannot succeed and the UI should prompt a reconnect instead. Consider branching so approved && reauthenticationRequired renders the Monerium KYC flow (reconnect copy), and only approved && !reauthenticationRequired goes 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
  • forUserAccessToken accepts any string, including an empty/whitespace token, which would silently send Authorization: Bearer and 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>
ebma added 12 commits September 16, 2026 09:25
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.
@ebma
ebma merged commit 7a29246 into staging Sep 17, 2026
6 checks passed
@ebma
ebma deleted the monerium-dual-app branch September 17, 2026 08:57
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.

2 participants