PROPOSAL · FOR NIUM · AVI REDDY
Money Movement 2.0 — High-Level Design
Scope. A payout control plane that sits beside Nium's existing payout pipeline and turns six new flow types — stablecoin settlement, marketplace split payouts, high-risk verticals, agent settlement, prediction-market resolution payouts, perpetuals collateral/PnL — into policies on one rail instead of six products. Built from Nium's public documentation and job postings only; every integration point below is a documented Nium API or a named public partner.
1. Design goals and non-goals
| Goal | How the design meets it |
|---|---|
| Every payout leaves an audit object | The trace is the unit of record: policy version, five decisions, evidence results, router trail (including partners skipped and why), route, cost, ledger postings, outcome + reason code. |
| New flows without new products | A policy (data, versioned) declares source, entity, evidence, timing, rail and pricing. Adding a vertical = adding a policy and an appetite row, reviewed by Risk. |
| No contagion to the core network | The appetite router only ever pairs a payout with a Nium entity + partner that has declared appetite for that vertical, jurisdiction, ticket, licence set and rail. Partners without appetite never see the flow. |
| Live inside Nium's constraints | Event-driven beside Transfer Money; netting before a wallet's 3 TPS; evidence fetched in parallel with TTL caches; idempotent on x-request-id. |
| Regulation as an input, not a memo | Jurisdiction exclusions (e.g. Brazil Res. 561 for stablecoin settlement), coin whitelists (GENIUS DASP dates), licence requirements (iGO, UKGC, MGA, CFTC DCM) are router/evidence data with effective dates. |
Non-goals: acquiring; issuing a stablecoin; replacing Transfer Money, Wallets, Onboarding or RFI; custody of digital assets (Nium's stated model is "Nium does not hold USDC").
1a. Design doctrine — six rules the plane is built by
| Rule | What it means here | Where it lives |
|---|---|---|
| 1. Own the decisions; rent nothing you already own. | Nium already owns the rails and the licences. What it rents today is the decision — the client decides, Nium executes. The plane moves the decision inside: the client declares, Nium decides. The rail is the last decision, not the product. | ControlPlane.evaluate — five decisions, one call |
| 2. Perimeter before product. | Nothing new ships as a product; it ships as a perimeter — entity × jurisdiction × vertical × currency × ticket band × evidence set × rails × timing — that a partner has opted into. A payout runs inside a perimeter or it doesn't run. A new vertical is a new perimeter, not a new product; a prohibition becomes a perimeter with a price. | perimeter.js, partners/appetite.json, trace.perimeter |
| 3. Cost at the granularity the rail emits. | No blended basis points. Every fee, spread, reserve and rail charge is a line, in the currency it is charged in, attributed to the decision that spent it (source, entity, timing, rail, policy). Appetite is priced from lines, not guessed. | priceRoute lines [label, amount, decision] |
| 4. Every later event cites the decision that created it. | A reserve release, a rail confirmation, a return or recall, a netting close, an event trigger, a re-check, a manual review — each is registered on the trace as a follow-on carrying the trace id. Attribution is the audit; the audit is what Risk and the bank partner buy. | trace.followOns, followOn() |
| 5. The identifier carries the decision. | Evidence attaches to the seller, the agent, the venue, the operator — not to the payout. Verify once, decide many; re-check on triggers, not on every call. The registries (sellers, agents, venues, operators) are the asset; payouts are the throughput. | evidence providers keyed by beneficiary / principal / venue; KYA registry |
| 6. What incumbents refuse at the cusp, the market funds next. | Every line on a prohibited list is a segment someone else is being paid to serve. The router exists so "no" can become "yes — on this partner, with this evidence, at this price", one perimeter at a time, without touching the core network. | router.js reason codes; appetite rows with effective dates |
2. Context — where it sits
flowchart LR
subgraph Clients
MK[Marketplaces & platforms]
TR[Corporate treasuries]
VN[Venues: prediction, perps, exchanges]
OP[Licensed operators]
AG[Agents / facilitators x402·MPP·AP2]
end
subgraph MM2[Money Movement 2.0 — control plane]
API[Control-plane API<br/>POST /payouts · GET /trace]
POL[Policy service]
EV[Evidence gate]
RT[Appetite router]
ST[Settlement orchestrator<br/>timing · netting · rail]
LG[Ledger & reserves]
TRC[Trace & audit store]
end
subgraph Nium[Nium platform — documented surface]
TM[Transfer Money / Batch / Validator / Rail preference]
WL[Wallets & multi-currency accounts]
ON[Customer Onboarding v5 · RFI v5]
VF[Nium Verify]
FW[Fund Wallet · VANs · USDC funding]
CD[Cards · Thredd · stablecoin cards]
KF[(Kafka · webhooks)]
RS[(Redshift · dbt)]
end
subgraph Partners
CB[Coinbase · Circle CPN]
BK[Bank & rail partners<br/>JPM · DBS · SCB · Banking Circle · ClearBank · Column · CFSB · Cross River · Cuscal · Pateno · Cobre · Netbank · GMO · BS2]
RG[Licence registries · venue attestations · oracles]
end
Clients --> API
KF --> API
API --> POL --> EV --> RT --> ST --> LG --> TRC
EV --> ON
EV --> VF
EV --> RG
ST --> TM
ST --> FW
ST --> CD
FW --> CB
TM --> BK
TRC --> RS
TRC --> KF
3. Components
| Component | Responsibility | Nium touchpoint (public) | Reference implementation |
|---|---|---|---|
| Control-plane API | Accept payout intents; idempotency; return traces | Same conventions as Nium API (x-api-key, x-request-id, unified validation errors) |
api/openapi.yaml, src/api/server.js |
| Policy service | Versioned policy registry + evaluator for the five decisions | — (new) | src/engine/policy.js, policies/*.json |
| Evidence gate | Run evidence providers in parallel; fold to PASS / HOLD / DECLINE with reason codes; open RFIs on HOLD | Onboarding v5, RFI v5, Verify, sanctions screening, OFI screening; registries and attestations via adapters | src/engine/evidence.js |
| Appetite router | Pair the payout with a Nium entity + partner with declared appetite; emit the trail | VAN/bank-partner table is the physical form of the appetite table | src/engine/router.js, partners/appetite.json |
| Settlement orchestrator | Timing (now / JIT / netting / event), rail selection under caps and windows, pricing (fee, reserve, FX at the net, rail fee), uneconomic-route guard | Transfer Money, Batch Payout, Payout Rail Preference, FX quotes/locks, Fund Wallet (USDC), Card balance load | src/engine/settlement.js |
| Ledger & reserves | Double-entry postings per currency; FX positions; rolling reserves with release dates | Wallet ledger; settlement reports | src/engine/ledger.js |
| Trace & audit store | Immutable trace per payout; export to Risk/partners/regulators | Redshift/dbt (reporting); webhooks (MM2_TRACE_*) |
src/engine/trace.js |
| Agent registry (KYA) | Agents mapped to KYB-verified principals; mandates; reputation | Customer Onboarding v5 for the principal | provider agentPrincipal, mandate |
| Netting engine | Accumulate streamed receipts; close on window or threshold; emit rail-sized payouts | Batch Payout API; wallet 3 TPS limit | NettingWindow |
4. The five decisions (per payout)
flowchart TD
A[Payout intent] --> S{1 · Source<br/>asset allowed?<br/>convert JIT / prefunded}
S -->|not allowed| D1[DECLINED · SOURCE_ASSET_NOT_ALLOWED]
S --> E{3 · Evidence<br/>providers in parallel<br/>fold: PASS · HOLD · DECLINE}
E -->|DECLINE| D2[DECLINED · reason code]
E --> R{2 · Entity<br/>appetite router}
R -->|no pairing| D3[DECLINED · JURISDICTION / REGISTRATION / VERTICAL]
R -->|above max ticket| H1[HELD · TICKET_ABOVE_MAX · EDD case]
E -->|HOLD| H2[HELD · RFI / pending]
R --> T{4 · Timing<br/>now · JIT · NET · EVENT}
T -->|event missing| H3[HELD · AWAITING_EVENT]
T -->|window open| Q[QUEUED · NETTING_QUEUED]
T --> L{5 · Rail<br/>choice ∩ policy ∩ partner<br/>caps · 24/7 windows · preference}
L -->|none| D4[DECLINED · RAIL_UNAVAILABLE]
L --> P[Price: fee · reserve · conversion · FX at the net · rail fee]
P -->|rail fees > 25% of value| H4[HELD · UNECONOMIC_ROUTE → fallback to netting]
P --> G[Ledger postings · balanced per currency]
G --> OK[APPROVED · trace]
5. Money-flow models the plane runs
- Pre-funded fiat payout — client wallet (safeguarded EMI money / US sponsor-bank custodial account) → Nium nostro at the destination partner → local rail.
- Just-in-time stablecoin funding — USDC to a static address → Circle/Coinbase custody + conversion → USD wallet → payout. Nium never holds the asset.
- Marketplace three-tier split — PSP sweep → Platform Receivables → Fees / Seller MCY / Base → seller payout by seller choice (bank, USDC, card).
- Event-triggered payout — resolution / delivery / attestation arms the payout; evidence gate; immediate rail.
- Streamed-then-netted — micro-receipts in escrow → one rail-sized payout per beneficiary per window (or threshold), FX at the net.
- Collateral on-ramp / PnL off-ramp — local RTP collection → USDC → venue; USDC PnL → local fiat, with venue attestation.
- Risk-reserved payout — fee + rolling reserve + partner-scoped appetite; sender pays on top; reserve released T+N.
- Scheme-level stablecoin settlement of Nium's own card book (SoFi/Mastercard pattern, Visa USDC settlement pilot) — a policy on the settlement asset, not a product.
6. Non-functional requirements
| NFR | Target | Mechanism |
|---|---|---|
| Decision latency | p50 < 50 ms with cached evidence; p99 < 400 ms with two live lookups | evidence providers async + TTL cache; router is pure |
| Throughput | 1,000 intents/s sustained; bursts (resolutions, streams) absorbed by netting and queues | Kafka partitions by beneficiary; netting windows; Batch Payout (1,000) |
| Availability | 99.95% for decisioning; margin-time SLA windows (weekend/overnight) explicitly priced and monitored | multi-AZ on AWS; degrade to HOLD (never silent approve) when a provider is down |
| Auditability | 100% of payouts have a trace; traces immutable; retention 7 years | append-only store; hash-chained per client |
| Idempotency | exactly-once decision per x-request-id for ≥ 24 h |
key-value cache keyed by client + request id |
| Security / privacy | PII minimisation in traces (evidence results, not documents); PCI scope unchanged (card widget stays Nium-hosted) | tokenised references into Onboarding/RFI |
| Regulatory change | effective-dated appetite and policy rows; dry-run mode (like Payout Validator) | policy versioning; validate endpoint |
7. Deployment on Nium's documented stack
Java/Spring Boot services on Kubernetes (AWS), MySQL for policy/appetite/trace metadata, Kafka for intents and trace events, Redis for evidence TTL cache and idempotency, S3 for trace archives, Redshift/dbt for reporting, Datadog/Prometheus for SLOs, Terraform, Jenkins/ArgoCD. The reference implementation here is dependency-free JavaScript so the same engine runs in tests, a CLI, an HTTP server and the browser demo; the production port is a one-to-one translation of src/engine/* into Spring services.
8. Rollout (matches the strategy's four quarters)
Q4 2026 — policy engine v1, trace on the existing payout path (shadow mode: decide, don't route), appetite router with three partners, stablecoin payout beta. Q1 2027 — marketplace seller-choice + lineage; two prediction-market pilots (US DCM B2B, LatAm). Q2 2027 — HRV GA with priced verticals and reserves; perps on/off-ramp; margin-time SLA. Q3 2027 — agent registry + facilitator + netting GA.
Money Movement 2.0 · an independent proposal by Avi Reddy for Nium. Not a Nium document. Confidential, shared for evaluation.