Skip to content

Commit 4eccd08

Browse files
Merge branch 'main' into docker-optimization-1335
2 parents 019e5cd + 6ee4dc7 commit 4eccd08

114 files changed

Lines changed: 10665 additions & 4817 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/SECURITY_SETUP_CHECKLIST.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# GitHub Security Setup Checklist
22

3-
This checklist helps ensure that GitHub's security features are properly configured for the FlowFi repository.
3+
This checklist helps repository administrators ensure that GitHub's security features are properly configured for the FlowFi repository. For the canonical security policy (reporting guidelines, supported versions, response timelines), see [SECURITY.md](../SECURITY.md). For the implementation status and next steps, see [SECURITY_IMPLEMENTATION_SUMMARY.md](../SECURITY_IMPLEMENTATION_SUMMARY.md).
44

55
## Repository Security Settings
66

.github/workflows/ci.yml

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ on:
1313
branches: [main, develop]
1414

1515
jobs:
16+
changes:
17+
name: Detect changes
18+
runs-on: ubuntu-latest
19+
outputs:
20+
backend: ${{ steps.filter.outputs.backend }}
21+
steps:
22+
- name: Checkout code
23+
uses: actions/checkout@v4
24+
25+
- name: Check changed files
26+
id: filter
27+
uses: dorny/paths-filter@v3
28+
with:
29+
filters: |
30+
backend:
31+
- 'backend/**'
32+
- 'package-lock.json'
33+
1634
frontend:
1735
name: Frontend CI
1836
runs-on: ubuntu-latest
@@ -34,10 +52,6 @@ jobs:
3452
run: npm run lint
3553
working-directory: frontend
3654

37-
- name: Install Rollup Native Binding
38-
run: npm install @rollup/rollup-linux-x64-gnu --no-save
39-
working-directory: frontend
40-
4155
- name: Run Frontend Tests
4256
run: npm run test:coverage
4357
working-directory: frontend
@@ -46,9 +60,17 @@ jobs:
4660
run: npm run build
4761
working-directory: frontend
4862

63+
- name: Check frontend bundle size
64+
run: bash ./scripts/check-bundle-size.sh
65+
working-directory: frontend
66+
4967
backend:
5068
name: Backend CI
5169
runs-on: ubuntu-latest
70+
needs: changes
71+
if: >-
72+
github.event_name == 'push' ||
73+
needs.changes.outputs.backend == 'true'
5274
services:
5375
postgres:
5476
image: postgres:16-alpine@sha256:e013e867e712fec275706a6c51c966f0bb0c93cfa8f51000f85a15f9865a28cb
@@ -99,9 +121,6 @@ jobs:
99121
cd ..
100122
git diff --exit-code -- backend/swagger/flowfi.openapi.json frontend/src/lib/api-types.generated.ts
101123
102-
- name: Install Rollup Native Binding
103-
run: npm install @rollup/rollup-linux-x64-gnu --no-save
104-
105124
- name: Run Backend Tests
106125
run: |
107126
ls -la src/generated/prisma

.github/workflows/pr-test-gate.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ jobs:
2727
runs-on: ubuntu-latest
2828
services:
2929
postgres:
30-
image: postgres:15
30+
# Pinned to match the version used in ci.yml and docker-compose.yml
31+
# (production target). Keep these in sync — see issue #1282.
32+
image: postgres:16-alpine@sha256:e013e867e712fec275706a6c51c966f0bb0c93cfa8f51000f85a15f9865a28cb
3133
env:
3234
POSTGRES_USER: postgres
3335
POSTGRES_PASSWORD: password
@@ -61,11 +63,6 @@ jobs:
6163
env:
6264
DATABASE_URL: postgresql://postgres:password@127.0.0.1:5432/flowfi_test
6365

64-
- name: Install Native Bindings
65-
run: |
66-
npm install @rollup/rollup-linux-x64-gnu --no-save
67-
working-directory: backend
68-
6966
- name: Run backend tests
7067
run: |
7168
ls -la src/generated/prisma

.github/workflows/security.yml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ jobs:
3333
run: npm ci
3434

3535
- name: Run npm audit (production dependencies)
36-
run: npm audit --omit=dev --audit-level=critical
36+
run: npm audit --omit=dev --audit-level=high
3737

3838
- name: Check for known vulnerabilities in frontend (production dependencies)
39-
run: npm audit --workspace=frontend --omit=dev --audit-level=critical
39+
run: npm audit --workspace=frontend --omit=dev --audit-level=high
4040

4141
- name: Check for known vulnerabilities in backend (production dependencies)
42-
run: npm audit --workspace=backend --omit=dev --audit-level=critical
42+
run: npm audit --workspace=backend --omit=dev --audit-level=high
4343

4444
- name: Setup Rust toolchain for contract audit
4545
uses: dtolnay/rust-toolchain@stable
@@ -62,6 +62,23 @@ jobs:
6262
- name: Verify security setup
6363
run: npm run verify-security
6464

65+
cargo-audit:
66+
name: Cargo Dependency Vulnerability Scan
67+
runs-on: ubuntu-latest
68+
steps:
69+
- name: Checkout code
70+
uses: actions/checkout@v4
71+
72+
- name: Install Rust stable
73+
uses: dtolnay/rust-toolchain@stable
74+
75+
- name: Install cargo-audit
76+
run: cargo install cargo-audit
77+
78+
- name: Run cargo audit on contracts
79+
run: cargo audit
80+
working-directory: contracts
81+
6582
codeql-analysis:
6683
name: CodeQL Analysis
6784
runs-on: ubuntu-latest

CONTRIBUTING.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,47 @@ Be constructive.
472472

473473
---
474474

475+
476+
477+
---
478+
479+
# 🤝 Contributor Badges
480+
481+
This project uses the [all-contributors](https://github.com/all-contributors/all-contributors) specification to recognize contributors.
482+
483+
## Triggering the Bot
484+
485+
After your pull request is merged or closed, comment on the merged PR or issue:
486+
487+
```
488+
@all-contributors please add @<your-username> for code
489+
```
490+
491+
The bot will add your profile to the Contributors table in the root `README.md`.
492+
493+
## Adding Sub-Package READMEs
494+
495+
Currently, only the root `README.md` has a Contributors section. If you add a Contributors section to a sub-package README (e.g., `backend/README.md`, `frontend/README.md`, `contracts/README.md`), update `.all-contributorsrc` in the repo root to include the additional path:
496+
497+
```json
498+
"files": [
499+
"README.md",
500+
"backend/README.md"
501+
]
502+
```
503+
504+
## Available Contribution Types
505+
506+
- `code` — Code contributions
507+
- `doc` — Documentation
508+
- `design` — Design
509+
- `bug` — Bug reports
510+
- `review` — Pull Request reviews
511+
- `test` — Testing
512+
- `infra` — Infrastructure / DevOps
513+
- `ideas` — Ideas & planning
514+
515+
475516
# Final Notes
476517

477518
- Contributions of all sizes are welcome

SECURITY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Security Policy
22

3+
This is the canonical security policy for FlowFi. It defines how vulnerabilities are reported, which versions are supported, and the responsible disclosure process. For contributors and administrators setting up repository security features, see [SECURITY_IMPLEMENTATION_SUMMARY.md](SECURITY_IMPLEMENTATION_SUMMARY.md). For the GitHub setup checklist, see [.github/SECURITY_SETUP_CHECKLIST.md](.github/SECURITY_SETUP_CHECKLIST.md).
4+
35
## Supported Versions
46

57
We actively support the following versions of FlowFi with security updates:

SECURITY_IMPLEMENTATION_SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Security Policy Implementation Summary
22

3-
This document summarizes the security policy implementation for FlowFi and provides next steps for repository administrators.
3+
This document is a **maintenance and onboarding guide** for repository administrators and contributors. It tracks which security measures have been implemented and what steps remain. It is not a replacement for the security policy itself. For the canonical security policy (reporting guidelines, supported versions, response timelines), see [SECURITY.md](SECURITY.md). For the GitHub security setup checklist, see [.github/SECURITY_SETUP_CHECKLIST.md](.github/SECURITY_SETUP_CHECKLIST.md).
44

55
## ✅ Completed Tasks
66

backend/docs/ARCHITECTURE.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Backend Architecture
2+
3+
This document is the canonical reference for backend service architecture. It is
4+
referenced by `backend/src/services/indexerService.ts` and
5+
`backend/src/services/soroban-indexer.service.ts` as the authoritative source for
6+
indexer ownership, SSE broadcast flow, and keeper-key authorization.
7+
8+
For the full project-wide architecture (event type data flows, pause/resume
9+
timing, environment variables, and operational runbook) see
10+
[`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md).
11+
12+
---
13+
14+
## Indexer Ownership Model
15+
16+
Three files with overlapping names handle indexing and indexer management. Only
17+
one of them is the source of truth for stream state.
18+
19+
| File | Role | Status |
20+
|------|------|--------|
21+
| `src/workers/soroban-event-worker.ts` (`SorobanEventWorker`) | **Source-of-truth indexer.** Polls Soroban RPC, decodes XDR events, persists `Stream` / `StreamEvent` rows, advances the `IndexerState` cursor, and broadcasts SSE updates. | **Active / source of truth.** Started by `src/workers/index.ts`. |
22+
| `src/services/soroban-indexer.service.ts` (`SorobanIndexerService`) | **Legacy indexer being phased out.** A simpler duplicate poller that writes to the same DB rows and races with the worker on the same `Stream` / `StreamEvent` records (issue #801). | **Legacy — do not extend.** Removal tracked with functional consolidation (issue #801). Started directly from `src/index.ts`. |
23+
| `src/services/indexerService.ts` | **Not an indexer at all.** Admin control-plane helpers (`getIndexerStatus`, `resetIndexer`, `replayFromLedger`) that read/reset the shared `IndexerState` cursor row and trigger the worker's poll loop. | **Active.** Name is misleading; kept alongside the legacy indexer above. |
24+
25+
### Key Rules
26+
27+
1. **When debugging indexing, read `src/workers/soroban-event-worker.ts`
28+
first.** It is the only file that persists canonical stream state.
29+
2. **Do not add new behavior to `soroban-indexer.service.ts`.** It exists only
30+
for backwards compatibility while the double-indexer race (issue #801) is
31+
consolidated. Mirror any changes in `SorobanEventWorker` instead.
32+
3. **`indexerService.ts` is control-plane only** — it never reads the chain; it
33+
manages the shared cursor and triggers replays.
34+
35+
### The Dual-Indexer Race
36+
37+
Both `SorobanEventWorker` and `SorobanIndexerService` poll the same Soroban RPC
38+
for the same contract events and write to the same `Stream` and `StreamEvent`
39+
rows. Because they run on independent timers, they can race:
40+
41+
- Both may process the same ledger simultaneously.
42+
- Both write to the same `Stream` row (upsert), so the last writer wins —
43+
usually harmless for immutable fields but problematic for additive mutations
44+
like `withdrawnAmount` (issue #808).
45+
- `StreamEvent` dedup via `@@unique([transactionHash, eventType])` prevents
46+
duplicate event rows, but does **not** protect stream state mutations.
47+
48+
**Mitigation:** Do not extend the legacy indexer. The consolidation (issue #801)
49+
will remove `SorobanIndexerService` entirely.
50+
51+
### Naming Convention Plan
52+
53+
The team convention is kebab-case with a `.service.ts` suffix. Once functional
54+
consolidation lands:
55+
56+
| Current Name | Expected Future Name |
57+
|---|---|
58+
| `indexerService.ts` | `indexer.service.ts` |
59+
| `soroban-indexer.service.ts` | *(removed)* |
60+
61+
---
62+
63+
## SSE Broadcast Flow
64+
65+
The SSE (Server-Sent Events) subsystem delivers real-time contract event
66+
notifications to connected frontend clients. The full SSE architecture (scaling,
67+
memory, security) is documented in
68+
[`docs/SSE_ARCHITECTURE.md`](./SSE_ARCHITECTURE.md).
69+
70+
### End-to-End Path
71+
72+
```
73+
Soroban RPC
74+
│ SorobanEventWorker polls for new contract events
75+
76+
SorobanEventWorker (src/workers/soroban-event-worker.ts)
77+
│ decode XDR → upsert Stream → insert StreamEvent
78+
79+
PostgreSQL (via Prisma)
80+
│ Stream + StreamEvent rows updated
81+
82+
SSE broadcast (src/services/sse.service.ts)
83+
│ sseService.broadcastToStream(streamId, event, data)
84+
│ sseService.broadcastToUser(publicKey, event, data)
85+
│ sseService.broadcastToAdmin(event, data)
86+
87+
├──► [Single instance] Direct write to in-memory client registry
88+
89+
└──► [Multi-instance] Redis Pub/Sub
90+
│ publish to sse:stream:<id>, sse:user:<address>
91+
92+
All backend instances subscribe
93+
│ rebroadcast to local connected clients
94+
95+
Frontend (useStreamEvents hook)
96+
```
97+
98+
### Broadcast Channels
99+
100+
The worker uses three broadcast entry points depending on the event:
101+
102+
| Method | When Used | Target Audience |
103+
|--------|-----------|-----------------|
104+
| `sseService.broadcastToStream(streamId, event, data)` | Stream lifecycle events (created, topped_up, withdrawn, cancelled, completed, paused, resumed) | Clients subscribed to that specific stream ID or `*` |
105+
| `sseService.broadcastToUser(publicKey, event, data)` | (Reserved for user-scoped events) | Clients subscribed to `user:<publicKey>` or `*` |
106+
| `sseService.broadcastToAdmin(event, data)` | Protocol-level events (fee_collected, fee_config_updated, admin_transferred) | The admin user identified by `ADMIN_PUBLIC_KEY` env var |
107+
108+
### Multi-Instance Fanout
109+
110+
When `REDIS_URL` is configured, broadcasts go through Redis Pub/Sub instead of
111+
direct in-memory writes:
112+
113+
1. The originating instance publishes `{ event, data }` to
114+
`sse:stream:<id>` or `sse:user:<address>`.
115+
2. Every backend instance subscribes via `psubscribe('sse:stream:*',
116+
'sse:user:*')` and rebroadcasts to its own local clients.
117+
3. This means events reach all connected clients regardless of which backend
118+
instance they are connected to.
119+
120+
### Client Connection Limits
121+
122+
| Limit | Default | Env Var |
123+
|-------|---------|---------|
124+
| Max SSE connections per server | 10,000 | `MAX_SSE_CONNECTIONS` |
125+
| Max connections per IP | 5 | Hardcoded |
126+
| Max connections per authenticated user | 10 | Hardcoded |
127+
128+
Slow clients (write buffer ≥ 64 KB) are automatically dropped to protect
129+
throughput for healthy clients.
130+
131+
---
132+
133+
## Keeper-Key Authorization Model
134+
135+
FlowFi splits transaction signing into two categories: custodial (server-signed)
136+
and non-custodial (wallet-signed). The signing key determines who is responsible
137+
for the transaction.
138+
139+
### Action Signing Matrix
140+
141+
| Action | Signer | Mechanism |
142+
|--------|--------|-----------|
143+
| **Top-up** | Server (custodial) | Backend submits the transaction using `KEEPER_SECRET_KEY`. The frontend sends only the stream ID and amount. |
144+
| **Withdraw** | Wallet (non-custodial) | Frontend builds and signs the transaction via the connected wallet (Freighter). The backend simulate endpoint exists for fee estimation only. |
145+
| **Pause / Resume** | Wallet (non-custodial) | Same as withdraw — frontend-signed. Backend simulate endpoints exist for fee estimation but do not submit. |
146+
| **Create stream** | Wallet (non-custodial) | Frontend signs via wallet and submits directly to the Soroban RPC. |
147+
148+
### The `KEEPER_SECRET_KEY`
149+
150+
- Stored as an environment variable on the backend.
151+
- Loaded by `src/services/sorobanService.ts` via
152+
`process.env.KEEPER_SECRET_KEY`.
153+
- Used **exclusively** by the top-up flow. The `topUpStream` function builds
154+
the transaction, signs it with the keeper keypair, and submits it to the
155+
Soroban RPC.
156+
- If `KEEPER_SECRET_KEY` is not configured, `topUpStream` throws
157+
`'KEEPER_SECRET_KEY not configured'` and the request returns HTTP 500.
158+
- The cancel endpoint (`src/controllers/stream/cancel.ts`) also reads
159+
`KEEPER_SECRET_KEY` but only to check whether the server wallet is configured;
160+
the actual cancel transaction is wallet-signed by the sender.
161+
162+
### Security Boundary
163+
164+
> **Do not wire pause/resume/withdraw to a server-side submit path.** Only
165+
> `top-up` is intentionally custodial. All other mutating actions must be
166+
> wallet-signed by the user to preserve the non-custodial security model.
167+
168+
The keeper key is a server-side secret and is never exposed to the frontend.
169+
It lives exclusively in the backend's environment configuration.
170+
171+
---
172+
173+
## Database Models
174+
175+
For a quick reference of the models involved in indexing:
176+
177+
| Model | Key Fields | Purpose |
178+
|-------|------------|---------|
179+
| `User` | `publicKey` | Stellar wallet addresses |
180+
| `Stream` | `streamId`, `sender`, `recipient`, `ratePerSecond`, `depositedAmount`, `withdrawnAmount`, `isActive` | Mirrors on-chain stream state |
181+
| `StreamEvent` | `streamId`, `eventType`, `transactionHash`, `ledgerSequence`, `timestamp` | Indexed on-chain events; unique on `(transactionHash, eventType)` |
182+
| `IndexerState` | `lastLedger`, `lastCursor` | Cursor for last successfully indexed ledger sequence |
183+
184+
---
185+
186+
## Related Documentation
187+
188+
- [Root Architecture](../../docs/ARCHITECTURE.md) — full project-wide architecture
189+
- [SSE Architecture](./SSE_ARCHITECTURE.md) — SSE scaling, security, operational runbook
190+
- [SSE Implementation](./SSE_IMPLEMENTATION.md) — client integration guide
191+
- [Authentication](./AUTHENTICATION.md) — SEP-10 + JWT auth flow

0 commit comments

Comments
 (0)