feat(ramps): poll neo-bank deposits and emit status notifications - #10120
Draft
saustrie-consensys wants to merge 5 commits into
Draft
feat(ramps): poll neo-bank deposits and emit status notifications#10120saustrie-consensys wants to merge 5 commits into
saustrie-consensys wants to merge 5 commits into
Conversation
Add emit-only Money Account deposit polling to RampsController for the MM
Neobank onramp flow (TRAM-3898, "poll for deposit and notifications"). A
sibling poll loop fetches each pollable autoramp's deposit/transaction records
from the neo-bank proxy, keeps a persisted state.deposits clone, and publishes
RampsController:depositStatusChanged on status transitions so the app can show
a toast or refresh the account screen. The poller takes no on-chain action;
vault sweeping stays with the backend.
- Add NeoBankService.getAutorampTransactions and the matching messenger action,
mapping proxy transactions (bare array or a { transactions } envelope) via the
exported mapNeoBankTransactionToRemoteSnapshot. Rejects items missing id/status.
- Add the moneyAccountDeposit model: MoneyAccountDeposit, MoneyAccountDepositStatus,
the pure applyDepositRemoteStatus diff, and helpers, mirroring autorampAccount.
- Add startDepositPolling / stopDepositPolling / refreshDeposits, plus
markDepositAsNotified (notify dedupe) and removeDeposit (prune persisted state),
a persisted state.deposits array, and the depositStatusChanged event, reusing
the order poller's 30s interval and error backoff.
- Poll only Approved autoramps, or ones with an in-flight local deposit, so an
in-flight deposit keeps being tracked even if its route later goes terminal.
4 tasks
…trancy) - Backfill the owning autoramp id onto deposit snapshots from the poll query key, so an in-flight deposit stays pollable once its route goes terminal even when the proxy omits autoramp_id. - Only write state when a snapshot is new or materially changed (new `changed` flag on applyDepositRemoteStatus), so unchanged deposits no longer churn updatedAt / stateChange on every 30s poll for the life of the autoramp. - Guard refreshDeposits with the same in-flight flag as the poll loop so an unlock catch-up cannot overlap the interval poll. - Correct the removeDeposit doc: a deposit under an actively polled autoramp is a live mirror of the proxy and re-syncs; removal only sticks once the autoramp is no longer pollable. - Note that a deposit first observed already-terminal is recorded for display without a notification (no prior local state to have transitioned from).
… contract Replace the assumed transactions contract with the one shipping in onramp-api PR #1124 (the neobank-proxy forwards raw MoonPay Enterprise verbatim): - Route: GET /neobank/autoramp-transactions?autoramp_id={id} (flat, not the assumed nested /autoramps/{id}/transactions), returning a MoonPay PagedList; read the `data` array (single page for now, next_cursor pagination is a follow-up). - Payout hash: flat `transaction_hash` (drop the invented payout_transaction_hash and nested payout.transaction_hash). - Status: the real 8 AutorampTransactionStatus values (FundsReviewInProgress, ConversionInProgress, PayoutInProgress, Completed, Failed, RejectedAml, RejectedFraud, RejectedMinAmount). The three Rejected* are terminal and notable; an unknown status falls back to a non-terminal value. This fixes the prior invented enum, which treated rejections as non-terminal and would have polled them forever without ever notifying. - Display fields (amount/currency/moneyAccountAddress) are intentionally left unmapped: MoonPay carries them as structured objects, and the mobile-safe DTO (TRAM-3925) will pin the wire names.
… hash) The prior alignment matched onramp-api #1124's internal mock fixture ({ data, next_cursor } + flat transaction_hash), not the real MoonPay/Iron AutorampTransaction the proxy forwards verbatim. Per Iron's OpenAPI spec the list is a PagedList with an `items` array and the payout hash is nested at `payout_crypto_transaction.transaction_hash`. Reading only `data` / a flat hash would have thrown on every real response (no deposit ever tracked) and never captured the payout hash. Make the mapping tolerant of both, since the proxy PR is unmerged and TRAM-3925 will re-shape it again: - Envelope: read `items` (real Iron), then `data` (proxy fixture), then a bare array. - Payout hash: read nested `payout_crypto_transaction.transaction_hash`, then flat `transaction_hash`. - Tests use the real `{ items, cursor, prev_cursor }` + nested hash as the primary case, with the `data` / flat / bare-array fallbacks covered too.
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.
Explanation
Part of the MM Neobank MVP0 (Brazil 🇧🇷) onramp flow. When a user funds a Money Account via Pix, the partner (Iron) pays out mUSD on Monad, and Core needs to notice those deposits so the app can react.
This adds emit-only deposit polling to
RampsController(TRAM-3898, "poll for deposit and notifications"). The vault "sweep" (the original step 3) was removed from the ticket, so this PR only detects and reports: it takes no on-chain action, and vault sweeping stays with the backend.How it works, reusing the autoramp/order patterns already in this package:
NeoBankService.getAutorampTransactions(autorampId)fetches a partner's deposit/transaction records, mapped via the exportedmapNeoBankTransactionToRemoteSnapshot(accepts a bare array or a{ transactions }envelope; rejects items missingid/status).moneyAccountDeposit.tsis a thin local clone model mirroringautorampAccount.ts: a status enum plus a pureapplyDepositRemoteStatusdiff returningstatusChanged/shouldNotify.startDepositPolling/stopDepositPolling, plusrefreshDepositsfor app-load catch-up) reuses the order poller's 30s interval and error backoff. Each cycle upserts a persistedstate.depositsarray and publishesRampsController:depositStatusChangedon status transitions. OnlyApprovedautoramps (or ones with an in-flight local deposit) are polled, so an in-flight deposit keeps being tracked even if its route later goes terminal.markDepositAsNotifieddedupes repeat notifications for the same status;removeDepositlets consumers prune the persisted list.The client tracks the real MoonPay/Iron
AutorampTransactionthat the neobank-proxy (onramp-api PR #1124) forwards verbatim:GET /neobank/autoramp-transactions?autoramp_id={id}, returning an IronPagedList(itemsarray) with the payout hash nested atpayout_crypto_transaction.transaction_hash, and the 8AutorampTransactionStatusvalues. Because #1124 is unmerged and its own fixtures use a transitionaldata/ flattransaction_hashshape, the mapper reads both envelopes (itemsthendatathen a bare array) and both hash locations (nested then flat) so it works whichever ships. Still mocked in unit tests until #1124 merges. Deferred to the mobile-safe DTO in TRAM-3925 (unstarted): the display field names (amount / currency / money-account, which MoonPay carries as structured objects), and cursor pagination — the poll reads the first page only for now.Known behaviors worth a reviewer's eye
depositStatusChangednotification (there is no prior local state to have transitioned from). Whether catch-up should toast is a product question.FundsReviewInProgress), so an unrecognized status keeps polling rather than being mistaken for a terminal outcome — best-effort until TRAM-3925 pins the wire values.state.depositsis a live mirror of the proxy for actively-polled (Approved) autoramps, so it grows with the user's deposit history.removeDepositprunes local state, but a deposit under a still-polled autoramp re-syncs on the next poll (it only sticks once the autoramp is no longer pollable).Hosts must delegate
NeoBankService:getAutorampTransactions(added toRAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS), orstartDepositPolling/refreshDepositsreject with a messenger "handler has not been delegated" error.References
main(feat(ramps-controller): add NeoBankService and wallet registration HTTP client #10031, feat(ramps-controller): autoramp controller methods and last-seen functionality #10032).GET /neobank/autoramp-transactions, Matt Ilagan) — not yet merged; mocked here.RampsController:sendPix, the opposite direction).main).Checklist