Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/architecture/04-networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ Tokens are short-lived and passed per request.
transitions, and `shareIn`s a single connectivity flow. It's published app-wide as
`LocalNetworkObserver`.

## Module-level docs

Each service module carries its own `README.md` with the full controller catalog and
internals:

- [`services/opencode`](../../services/opencode/README.md) — OCP: intents, the
`SubmitIntent` handshake, transactors, Solana programs, swaps, exchange.
- [`services/flipcash`](../../services/flipcash/README.md) — accounts, chat,
contacts, profiles; signing path and event streaming.
- [`services/opencode-compose`](../../services/opencode-compose/README.md) ·
[`services/flipcash-compose`](../../services/flipcash-compose/README.md) — Compose bindings.

## Why this matters

The four-layer split means a proto or transport change is absorbed at the Api/
Expand Down
25 changes: 25 additions & 0 deletions services/flipcash-compose/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# services/flipcash-compose

The intended **Compose-bindings layer** over [`services/flipcash`](../flipcash/README.md),
mirroring what [`services/opencode-compose`](../opencode-compose/README.md) does for
opencode.

> Namespace `com.flipcash.services.flipcash.compose`.

> **Status: currently a shell.** This module has **no `src/` yet** — only a
> `build.gradle.kts` that `api(...)`-exports `:services:flipcash` and
> `:services:opencode-compose`. It exists so feature/UI code can depend on a single
> Compose-aware entry point for the Flipcash backend.

## What belongs here

Compose-facing bindings for `:services:flipcash` — the same shape as
`opencode-compose`'s `LocalExchange`: `staticCompositionLocalOf` handles and
`@Composable` providers/`remember*` helpers that expose Flipcash controllers/state to
the composition tree (see the CompositionLocal pattern in
[02 — State & dependency injection](../../docs/architecture/02-state-and-dependency-injection.md)).
Keep the Compose runtime here, out of the Compose-free `:services:flipcash` core.

## See also

- [`services/flipcash`](../flipcash/README.md) · [`services/opencode-compose`](../opencode-compose/README.md) · [02 — State & DI](../../docs/architecture/02-state-and-dependency-injection.md)
107 changes: 107 additions & 0 deletions services/flipcash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# services/flipcash

The Android client for the **Flipcash app backend** — accounts/auth, profiles,
contacts, chat & messaging, activity feed, moderation, push, settings, and
third-party (on-ramp) integration. It builds on [`services/opencode`](../opencode/README.md)
(re-exported via `api(...)`) for the core gRPC infrastructure and Solana models.

> Namespace `com.flipcash.services.*`. Layering (API → Service → Repository →
> Controller) is described in
> [04 — Networking](../../docs/architecture/04-networking.md); the auth state machine
> and account model in [06 — Payments & operations](../../docs/architecture/06-payments-and-operations.md).

```mermaid
graph TD
Feature["feature / shared coordinator"]
Ctrl["controllers/* (public)"]
Repo["repository/* (interfaces)"]
IRepo["internal/repositories/* (+ mappers)"]
Svc["internal/network/services/* (Result + typed errors)"]
Api["internal/network/api/* (gRPC stubs + signing)"]
Backend["Flipcash backend"]

Feature --> Ctrl --> Repo --> IRepo --> Svc --> Api --> Backend
```

## Public API — controllers

Fourteen controllers, each backed by a `repository/` interface (implemented by an
`internal/repositories/InternalXxxRepository`):

| Controller | Area |
|------------|------|
| [`AccountController`](src/main/kotlin/com/flipcash/services/controllers/AccountController.kt) | Register, login, get user flags |
| `ProfileController` | Display name, profile, social-account linking |
| `ContactListController` | Contact CRUD; checksum / delta / full upload |
| `ContactVerificationController` | Email & phone verification |
| `ResolverController` | Contact resolution |
| `ChatController` / `ChatMessagingController` | Chat retrieval; send/receive, typing, pointers |
| `EventStreamingController` | **Streaming** chat updates (`Flow<ChatUpdate>`) |
| `ActivityFeedController` | Activity feed |
| `ModerationController` | Text / image moderation |
| `PushController` | Push-token registration |
| `PurchaseController` | In-app-purchase acknowledgement |
| `SettingsController` | Locale / region settings |
| `ThirdPartyController` | On-ramp providers, liquidity pools |

## Models, auth & state

- **`models/`** — public domain types: `UserFlags` (server-driven account flags &
entitlements, e.g. `isStaff`, `enablePhoneNumberSend`), `UserProfile`,
the `chat/` models (`ChatMetadata`, `ChatMessage`, `ChatUpdate`, …), `Jwt`,
`QueryOptions`/`PagingToken`, and `Errors.kt` — **50+ sealed error hierarchies**
(`LoginError`, `RegisterError`, `SendMessageError`, …) over `CodeServerError`
([14 — Error handling](../../docs/architecture/14-error-handling.md)).
- **`user/UserManager`** — the singleton auth **state machine** (`AuthState`:
`Unknown → Onboarding → Authenticating → Ready → LoggedOut`), exposing
`state: StateFlow`, the `mnemonic`/`entropy`, `accountCluster`, `userFlags`,
`profile`. It's the source of truth for "am I logged in?" across the app
([06](../../docs/architecture/06-payments-and-operations.md)).
- `modals/ModalManager`, `persistence/` (`DataSource` abstractions), `validators/`.

## DI & channels

`inject/FlipcashModule` (`internal`) provides two channels into `SingletonComponent`:

- `@FlipcashManagedChannel` — unary (`idleTimeout 5m`, `keepAliveWithoutCalls=false`).
- `@FlipcashManagedStreamingChannel` — streaming (`keepAliveWithoutCalls=true`).

Both target the Flipcash base URL with the `LoggingClientInterceptor`, configured via
`@FlipcashProtocol ProtocolConfig`. It also **binds** the 14 repository interfaces to
their `Internal*` implementations. The module's `build.gradle.kts` `api(...)`-exports
`:services:opencode` and `:libs:network:jwt`.

## Deep dive: signing & the request lifecycle

Requests are signed with the owner's **Ed25519** key at the API boundary.
`internal/network/extensions/` holds the helpers: `sign(owner)` /
`authenticate(owner)` (build `Common.Signature` / `Common.Auth`) plus
`LocalToProtobuf` / `ProtobufToLocal` converters between domain and generated types.
A typical call — e.g. `AccountController.register` → `AccountService.register` →
`AccountApi.register` — builds the protobuf request, validates it (`protovalidate`),
signs it, sends it on the unary channel, and folds the response into a
`Result<ID>` with a typed error on failure (`RegisterError.*`).

## Deep dive: event streaming

`EventStreamingController` exposes chat updates as a `Flow<ChatUpdate>` over the
streaming channel. The stream is opened/closed explicitly and its **lifecycle is the
caller's responsibility** (a coordinator owns the scope and re-opens on
foreground/reconnect).

## Adding an RPC

1. **Proto** — update `definitions/flipcash`, regenerate
([13](../../docs/architecture/13-protobuf-and-codegen.md)).
2. **Api** — add the call to `internal/network/api/XxxApi` (build request, sign, validate).
3. **Service** — map the response to `Result<T>` + a typed error in
`internal/network/services/XxxService`.
4. **Repository** — add to the `repository/` interface and `internal/repositories/InternalXxxRepository`
(+ a mapper if the model needs translating).
5. **Controller** — expose it on the public `controllers/XxxController`; bind any new
repository in `FlipcashModule`.

## See also

- [04 — Networking](../../docs/architecture/04-networking.md) · [06 — Payments & operations](../../docs/architecture/06-payments-and-operations.md) · [13 — Protobuf & codegen](../../docs/architecture/13-protobuf-and-codegen.md) · [14 — Error handling](../../docs/architecture/14-error-handling.md)
- Core infra & models: [`services/opencode`](../opencode/README.md) · Compose bindings: [`services/flipcash-compose`](../flipcash-compose/README.md)
29 changes: 29 additions & 0 deletions services/opencode-compose/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# services/opencode-compose

A thin Compose-bindings layer over [`services/opencode`](../opencode/README.md). It
re-exports the opencode API (`api(:services:opencode)`) and surfaces the things
features read from the composition tree rather than inject directly.

> Namespace `com.getcode.opencode.compose`. See the CompositionLocal injection
> pattern in [02 — State & dependency injection](../../docs/architecture/02-state-and-dependency-injection.md).

## What's here

- **`LocalExchange`** (`Exchange.kt`) — `staticCompositionLocalOf<Exchange>` that
exposes the OCP [`Exchange`](../opencode/README.md) (rates, preferred currency) to
Compose. Provided in `MainActivity`'s `CompositionLocalProvider`; read with
`LocalExchange.current`.
- **`ExchangeStub`** (`ExchangeStub.kt`) — an inert `Exchange` (empty/identity rates)
used as the default when no real `Exchange` is provided (previews, tests).

## Why a separate module

`:services:opencode` is Compose-free (it's a plain library). This module adds the
Compose dependency and the `Local*` bindings, keeping the Compose runtime out of the
core service module. Features depend on `:services:opencode-compose` (often
transitively via `:services:flipcash-compose`) to get both the API and its ambient
handles.

## See also

- [`services/opencode`](../opencode/README.md) · [02 — State & DI](../../docs/architecture/02-state-and-dependency-injection.md) · [06 — Payments & operations](../../docs/architecture/06-payments-and-operations.md)
164 changes: 164 additions & 0 deletions services/opencode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# services/opencode

The Android client for the **Open Code Protocol (OCP)** — the gRPC backend that
handles money movement: transactions/intents, swaps, exchange rates, accounts, and
the Solana transaction construction that backs them. This is the deeper of the two
service modules; `services/flipcash` wraps the Flipcash app backend and depends on
this one.

> Namespace `com.getcode.opencode.*`. For the layering this module follows
> (API → Service → Repository → Controller) see
> [04 — Networking](../../docs/architecture/04-networking.md); for the currency model
> (USDF, launchpad tokens, `AccountCluster`) see
> [06 — Payments & operations](../../docs/architecture/06-payments-and-operations.md).

```mermaid
graph TD
Feature["feature / shared coordinator"]
Ctrl["controllers/* (public, stateful)"]
Repo["repositories/* (interfaces)"]
Svc["internal/network/services/*"]
Exec["internal/network/executors/* (bidi)"]
Api["internal/network/api/* (gRPC stubs)"]
Intents["solana/intents/* + internal/solana/programs/*"]
Backend["OCP backend + Solana"]

Feature --> Ctrl --> Repo --> Svc --> Api --> Backend
Svc --> Exec --> Api
Exec --> Intents
```

## Public API — controllers

Features consume **controllers** (and the `Exchange` interface); everything under
`internal/` is off-limits.

| Controller | Purpose | Key surface |
|------------|---------|-------------|
| [`TransactionController`](src/main/kotlin/com/getcode/opencode/controllers/TransactionController.kt) | Submits intents (transfer, withdraw, remote send/receive, buy/sell), tracks limits | `submitIntent(...)`, `buy/sell/withdraw`, `limits: StateFlow`, implements [`TransactionOperations`](src/main/kotlin/com/getcode/opencode/controllers/TransactionOperations.kt) |
| [`AccountController`](src/main/kotlin/com/getcode/opencode/controllers/AccountController.kt) | Account clusters, linking/unlinking, account-state polling | active-cluster + account-list `StateFlow`s |
| [`TokenController`](src/main/kotlin/com/getcode/opencode/controllers/TokenController.kt) | **Stateless** network gateway for token metadata / balances | flows only; caching is the consumer's job (the `TokenCoordinator`) |
| [`CurrencyController`](src/main/kotlin/com/getcode/opencode/controllers/CurrencyController.kt) | Rates, currencies, live/historical mint data | `streamLiveMintData()`, rate flows |
| [`MessagingController`](src/main/kotlin/com/getcode/opencode/controllers/MessagingController.kt) | P2P bill messaging streams (give/grab/claim) | open/poll/ack message stream |

Repositories (`repositories/`) are the interface seam beneath the controllers:
`Transaction`, `Account`, `Currency`, `Messaging`, `Swap`, `Event` — each implemented
by an `Internal*Repository` under `internal/domain/repositories`.

## Models

Public domain models live under `model/` (see
[06](../../docs/architecture/06-payments-and-operations.md) for the economic model):

- **`model/financial/`** — `MintMetadata` (alias `Token`; `Token.usdf`),
`LaunchpadMetadata` (USDF-backed bonding-curve fields), `Fiat`/`LocalFiat`/
`VerifiedFiat`, `Rate`, `Currency`/`CurrencyCode`, `Fee`, `Limits`, `Distribution`.
- **`model/accounts/`** — `AccountCluster` (authority + timelock + per-token deposit
addresses), `TimelockDerivedAccounts`, `AccountType`, `GiftCardAccount`, `PoolAccount`.
- **`model/transactions/`** — `TransactionMetadata`, `SwapMetadata`/`SwapState`,
`ExchangeData` (`Verified`/`Unverified`).
- **`model/core/`** — `OpenCodePayload` (the QR/rendezvous payload), `ID`.

## DI & channels

`inject/` provides three Hilt modules into `SingletonComponent`:

- **`OpenCodeModule`** — `Exchange`, `ProtocolConfig` (`@OpenCodeProtocol`), and the
two gRPC channels: `@OpenCodeManagedChannel` (unary) and
`@OpenCodeManagedStreamingChannel` (keep-alive for streams), both built with
`AndroidChannelBuilder`/`OkHttpChannelBuilder` and the `LoggingClientInterceptor`.
- **`TransactorModule`** — transactors + `PayloadFactory`, `AccountClusterFactory`.
- **`SessionListenersModule`** — the `SessionListener` set (login/logout hooks).

> **`ed25519.shadow` plugin:** the build applies `flipcash.android.ed25519.shadow`,
> which shades the native Ed25519 library so this module's crypto can't collide with
> other consumers' versions. It's the only module that needs it.

## Deep dive: the `SubmitIntent` bidirectional handshake

Money movement is a **bidirectional stream**, not a fire-and-forget RPC. The client
builds and signs the Solana transactions locally using server-provided parameters.
`IntentExecutor` (`internal/network/executors/`) drives it over a
`BidirectionalStreamReference` (`internal/bidi/`):

```mermaid
sequenceDiagram
participant C as Client (IntentExecutor)
participant S as OCP server
C->>S: SubmitActions (intent id, actions, signature)
S->>C: ServerParameters (per-action nonces, params)
Note over C: apply params, build txns, sign locally
C->>S: SubmitSignatures
S->>C: Success (or Error)
```

On `Error`, `SubmitIntentError.typed(...)` maps the code to a typed failure; the
`InternalTransactionRepository` **retries on `StaleState` race conditions**
(`retryableOrThrow`, see [14 — Error handling](../../docs/architecture/14-error-handling.md)).

### Intents & actions
An **intent** is an `IntentType` (`solana/intents/`) holding an `ActionGroup` of
`ActionType`s. Kinds (`internal/network/api/intents/`): `IntentTransfer`,
`IntentWithdraw`, `IntentRemoteSend`/`IntentRemoteReceive`, `IntentDistribution`,
`IntentCreateAccount`, `IntentStatefulSwap`/`IntentStatelessSwap`, `IntentFundSwap`.
Actions (e.g. `ActionPublicTransfer`, `ActionPublicWithdraw`, `ActionOpenAccount`,
`ActionFeePayment`) each construct their transaction from server parameters and
contribute Ed25519 signatures.

### Solana programs
`internal/solana/programs/` (~33 builders) encode the on-chain instructions:
`TimelockProgram` (vault lifecycle), `VirtualMachineProgram` (VM swaps),
`TokenProgram`/`AssociatedTokenProgram` (SPL), `CurrencyCreatorProgram`
(`InitializeCurrency`/`BuyTokens`/`SellTokens`/`InitializePool`),
`CoinbaseStableSwapperProgram` (USDC↔USDF), `ComputeBudgetProgram`, `SystemProgram`,
`MemoProgram`.

## Deep dive: transactors

A **transactor** (`internal/transactors/`) is a coroutine-scoped state machine for a
multi-step, multi-RPC workflow that coordinates **messaging + an intent**. Lifecycle:
`with(...)` configures (token, amount, rendezvous payload), `start()` runs the flow,
`dispose()` cancels the scope.

- `GiveBillTransactor` / `GrabBillTransactor` — the device-to-device cash-bill
exchange: advertise on the messaging stream, wait for the counterpart, then submit
the transfer/remote-receive intent.
- `SendGiftCardTransactor` / `ReceiveGiftCardTransactor` — gift-card (link) flow.
- `Transactor<E>` is the base (provides `logAndFail`); `PayloadFactory` builds the
`OpenCodePayload`, `AccountClusterFactory` derives clusters.

## Deep dive: swaps

Buy/sell of a launchpad currency against USDF runs through `StatefulSwapExecutor`
(escrowed state machine: `SwapState` CREATED→FUNDED→COMPLETED) or
`StatelessSwapExecutor` (atomic, signed up-front). `SwapRepository`/`SwapService`
expose it; `SwapFundingSource` selects how the swap is funded.

## Deep dive: exchange & verified rates

`exchange/Exchange` (impl `internal/exchange/OpenCodeExchange`) streams and caches
rates and tracks the preferred currency. Every payment carries a
**cryptographically-signed rate proof**: `VerifiedProtoManager` seals a `VerifiedState`
into an immutable `VerifiedFiat` (via `VerifiedFiatCalculator`), so the amount and the
rate it was computed at can't drift. See
[06](../../docs/architecture/06-payments-and-operations.md).

`providers/` exposes `SessionListener` (the login/logout hook coordinators implement)
and `TokenMetadataProvider`.

## Adding an intent / RPC

1. **Proto** — update `definitions/opencode` and regenerate
([13](../../docs/architecture/13-protobuf-and-codegen.md)).
2. **Intent** — add an `IntentType` + its `ActionType`s under
`internal/network/api/intents/`; add any new `internal/solana/programs/` instruction.
3. **Api/Service/Executor** — extend `TransactionApi` and `TransactionService`
(or add an executor for a new streaming flow).
4. **Repository** — add the method to the repository interface + `Internal*Repository`
(with typed-error mapping / retry where relevant).
5. **Controller** — expose it on the public controller.

## See also

- [04 — Networking](../../docs/architecture/04-networking.md) · [06 — Payments & operations](../../docs/architecture/06-payments-and-operations.md) · [13 — Protobuf & codegen](../../docs/architecture/13-protobuf-and-codegen.md) · [14 — Error handling](../../docs/architecture/14-error-handling.md)
- Sibling: [`services/flipcash`](../flipcash/README.md) · Compose bindings: [`services/opencode-compose`](../opencode-compose/README.md)
Loading