README.md: eExchange — Exchange8 Trading App
1. Summary
eExchange is the Exchange8 vertical's Flutter client for traders and
exchange operations staff: live order blotter, order entry ticket, depth
ladder with BBO, trade tape, and the operations console (halts, busts). It
is a BFF-only client — every backend call routes through Sentin8:BFF
(/proxy/exchange8/..., /subscribe SSE); no service is ever addressed
directly. The app is a composition of the platform Flutter library
(sdk/flutter/sentin8_flutter) with the Exchange8 brand.
2. Motivation
Exchange8 needs the trader and operations surfaces available anywhere — mobile/desktop — without a FIX console or a fat desktop terminal:
- A trader must see live order state (blotter), submit orders, and read the market (depth, BBO, tape) from a thin, tenant-aware client.
- Operations staff need halt/resume and trade-bust actions plus a read surface for surveillance alerts.
- OAuth tokens never reach the device — the BFF holds tokens server-side (architecture-principles §4.7), so the app keeps no secrets.
- Every backend surface is already scope-gated; feature gating in the app follows the provisioned scope map, while the server remains the sole authorization decision (Shield).
3. Architecture & High-Level Design
A single-page Flutter shell with five pages. One shared Bff8ApiClient
façade wraps the platform transport with Exchange8 channels and holds the
client-side caches (orders, books, tapes) the pages render. SSE
subscriptions deliver blotter, book, and tape updates; mutations go out as
BFF-proxied HTTP POSTs.
- AppShell — bottom navigation across the five surfaces; provides the
BffClientScopeinherited widget so all pages share one client instance. - Bff8ApiClient (app-local façade) — wraps the platform
sdk/flutter/sentin8_fluttertransport: typedget/post,subscribe(channel)SSE stream, and the order/book/tape caches. - Pages —
StreamBuilderover SSE channels (blotter, depth, tape), forms issuing BFF POSTs (order entry, bust), and aFutureBuilderGET (alerts).
4. Key Traits and Data Structures
Bff8ApiClient(app-local): the façade pages use — HTTP verbs, SSE subscription, and the order/book/tape caches. Concrete transport comes fromsdk/flutter/sentin8_flutter.BffClientScope:InheritedWidgetcarrying the authenticated client down the widget tree;BffClientScope.of(context)is the pages' single access point.BffEvent: one SSE event — channel name + decoded JSON payload.OrderRow: the blotter row (cl_ord_id, status, qty, limit, timestamp).BffError: transport/HTTP errors surfaced to the UI as messages.
5. Rationale, Trade-offs, and Alternatives
- BFF-only access (rejected: direct service calls). Services are not
exposed to clients, tokens never reach the device, and tenant resolution
- scope down-scoping stay server-side.
- InheritedWidget scope (rejected: global singleton). A root-scoped inherited widget keeps the client explicit and injectable in widget tests — no mutable global state.
- Façade-owned caches (rejected: per-page ephemeral state). SSE events arrive incrementally while blotter/ladder/tape need the accumulated picture regardless of which page mounted first; one façade-owned cache keeps every page consistent.
- SSE for market data (rejected: WebSocket). Depth/BBO/tape/blotter are
one-way server→client push — the platform subscription tier (
/subscribe) is SSE; WebSocket is reserved for the bidirectional chat tier (architecture-principles §4.7a). - StreamBuilder over a state-management library. Each surface is one event stream plus a small façade-owned cache; a heavyweight store adds ceremony with no cross-page write contention to manage.
6. Internal Implementation & Invariants (Private Design)
- Single client instance.
AppShellcreates exactly one façade; pages obtain it viaBffClientScope.of(context)and never construct their own. - Cache ownership. Order/book/tape caches are façade-owned; pages render read-only views (sorted/sliced at build time); SSE handlers are the only writers.
- Idempotent order submission. The ticket generates
cl_ord_idper submit and the gateway dedupes on it — a retry after transport failure must reuse the id. - No secrets on device. No tokens, session keys, or FIX credentials are persisted by the app; the platform shell owns authentication.
- Scope-driven gating. Feature enablement follows the provisioned scope map; pages never call endpoints outside their scope.
7. Unresolved Questions / Future Designs
- Transport bootstrap: the shell constructs the façade with a placeholder transport; wiring the authenticated platform transport (sign-in, org-env picker, refresh) is pending (DESIGN.md §10).
- Pegged/iceberg entry and a pre-submit risk preview are not yet on the ticket; cancel/amend and the order history drawer are not yet rendered.
- Whether mobile parity for sessions/feeds/customers admin (Dash RFW territory today) is wanted.
- Golden/widget tests under
test/are pending (DESIGN.md §9).
Wire-level detail: DESIGN.md. Business functionality: BUSINESS.md.
App-Store listing manifest: app-listing.json (seeded via make seed).
Epic/phase history for this app: BACKLOG.md epic A1 at the workspace root.
Design attribution: exchange8:eexchange and its underlying concepts were designed by Hamid Jahanzad Hamid@Jahanzad.com.