Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7b77370
Add investor portfolio dashboard scaffold with sorting, filtering, pa…
Jun 21, 2026
4cb9ee2
feat(notifications): add real-time WebSocket invoice event notificati…
Mhidesav Jun 22, 2026
050ee61
test: implement invoice lifecycle e2e tests
Jun 22, 2026
9d7622d
feat: implement role-based access control for frontend and backend
Jun 22, 2026
f1815fb
feat: implement investor pool metrics dashboard
Jun 22, 2026
a79db73
feat(frontend): add multi-step invoice wizard with validation and review
bigvictoh Jun 22, 2026
f565337
test(contracts): add Soroban invoice & financing-pool contracts with …
Manuel1234477 Jun 22, 2026
2fe2ba4
feat(invoice): SEP-0041-style NFT tokenization of funded invoices
Manuel1234477 Jun 22, 2026
74190a1
fix(backend): sync invoice status to REPAID on on-chain settlement
Manuel1234477 Jun 22, 2026
209a4a1
feat(contracts): add Soroban smart contracts with systematic auth and…
jotel-dev Jun 23, 2026
721f608
Merge pull request #28 from dev-fatima-24/issue-12
milah-247 Jun 23, 2026
d6d6bcb
Merge pull request #32 from Manuel1234477/feat/soroban-contract-test-…
milah-247 Jun 23, 2026
b515932
Merge pull request #33 from Manuel1234477/feat/invoice-nft-tokenization
milah-247 Jun 23, 2026
79df522
Merge branch 'main' into fix/settlement-status-sync
milah-247 Jun 23, 2026
3131fd0
Merge pull request #34 from Manuel1234477/fix/settlement-status-sync
milah-247 Jun 23, 2026
b5264a4
Merge pull request #23 from Danitello123/investor-portfolio-table
milah-247 Jun 23, 2026
605d8e4
Merge branch 'main' into main
milah-247 Jun 23, 2026
a75d50f
Merge pull request #35 from Benedict315/main
milah-247 Jun 23, 2026
1e7bfa7
Merge branch 'main' into issue-18-invoice-wizard
milah-247 Jun 23, 2026
88aea03
Merge pull request #31 from bigvictoh/issue-18-invoice-wizard
milah-247 Jun 23, 2026
c594916
Merge pull request #30 from dev-fatima-24/issue-6
milah-247 Jun 23, 2026
3b86482
Merge pull request #29 from dev-fatima-24/issue-10
milah-247 Jun 23, 2026
a39738a
Merge branch 'main' into feat/ws-invoice-notifications
milah-247 Jun 23, 2026
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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ HORIZON_URL=http://localhost:8000
PORT=4000
JWT_SECRET=change_me_to_a_strong_random_secret

# Settlement event listener (issue #3). Optional — sane defaults apply.
# Comma-separated Soroban contract ids to filter events by (default: all).
INVOICE_CONTRACT_ID=
# How often the listener polls Soroban RPC for settlement events (ms).
SETTLEMENT_POLL_INTERVAL_MS=5000
# Per-event retry attempts and exponential-backoff base delay (ms).
SETTLEMENT_MAX_ATTEMPTS=3
SETTLEMENT_RETRY_BASE_MS=500

# ── Frontend ─────────────────────────────────────────────────
NEXT_PUBLIC_API_URL=http://localhost:4000
NEXT_PUBLIC_HORIZON_URL=http://localhost:8000
54 changes: 54 additions & 0 deletions .github/workflows/app-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Backend & Frontend CI

on:
push:
paths:
- "backend/**"
- "frontend/**"
- ".github/workflows/app-ci.yml"
pull_request:
paths:
- "backend/**"
- "frontend/**"
- ".github/workflows/app-ci.yml"

jobs:
backend:
name: Backend Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: backend/package-lock.json

- run: npm ci

- name: Run tests
run: npm test

frontend:
name: Frontend Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: frontend/package-lock.json

- run: npm ci

- name: Run tests
run: npm test
48 changes: 48 additions & 0 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Soroban Contracts

on:
push:
branches: [main]
paths:
- "contracts/**"
- ".github/workflows/contracts.yml"
pull_request:
paths:
- "contracts/**"
- ".github/workflows/contracts.yml"

defaults:
run:
working-directory: contracts

jobs:
test:
name: Format, lint & test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cache cargo registry and build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
contracts/target
key: ${{ runner.os }}-cargo-${{ hashFiles('contracts/**/Cargo.toml') }}
restore-keys: |
${{ runner.os }}-cargo-

- name: Check formatting
run: cargo fmt --all --check

- name: Clippy (warnings as errors)
run: cargo clippy --all-targets -- -D warnings

- name: Run unit tests
run: cargo test --all
7 changes: 7 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
dist/
coverage/
*.log
.env
# Prisma generated client
src/generated/
80 changes: 80 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# InvoiceFi-Stellar Backend

NestJS + Prisma (PostgreSQL) backend. Its primary job is to keep the off-chain
invoice records in sync with on-chain state — in particular, transitioning
invoices to `REPAID` when the settlement contract repays them on-chain (issue
#3) — and to serve invoice/dashboard data over REST.

## Stack

- **NestJS 11** — HTTP API + scheduled jobs
- **Prisma 6 / PostgreSQL** — persistence
- **@stellar/stellar-sdk** — Soroban RPC `getEvents` + XDR decoding
- **Jest** — unit tests

## Settlement event listener (issue #3)

`src/settlement/` polls Soroban RPC and applies settlements:

- `soroban-events.service.ts` — calls `rpc.Server.getEvents`, decodes each
event's XDR topics/value into native values (`NormalizedEvent`).
- `settlement-event.parser.ts` — pure function that recognizes `InvoiceSettled`
events and extracts the invoice id.
- `settlement.service.ts` — `settleInvoice()` transitions `FUNDED → REPAID` via
a single conditional `updateMany` inside a transaction. **Atomic** (guarded on
`status = FUNDED`) and **idempotent** (a replayed event for an already-`REPAID`
invoice is a no-op).
- `settlement-sync.service.ts` — polls every `SETTLEMENT_POLL_INTERVAL_MS`
(default 5s, so dashboards reflect settlement within ~10s). Each event is
settled through `withRetry` (max 3 attempts, exponential backoff). The ledger
cursor only advances past fully-processed ledgers, so a permanently-failing
event is retried on the next cycle instead of being skipped.
- `sync-cursor.service.ts` — persists the last processed ledger (`SyncCursor`).

Dashboards read current status via `src/invoices/` REST endpoints:

| Method & path | Purpose |
| -------------------------------------- | -------------------------------- |
| `GET /health` | Liveness (used by compose). |
| `GET /invoices` | All invoices. |
| `GET /invoices/:onchainId` | Single invoice by on-chain id. |
| `GET /dashboard/farmer/:address` | A farmer's invoices. |
| `GET /dashboard/investor/:address` | An investor's invoices. |

## Environment

See the repo-root `.env.example`. Key variables: `DATABASE_URL`,
`STELLAR_RPC_URL`, `PORT`, and optional listener tuning:

| Variable | Default | Meaning |
| ------------------------------ | ------- | ----------------------------------------- |
| `INVOICE_CONTRACT_ID` | (all) | Comma-separated contract ids to filter. |
| `SETTLEMENT_POLL_INTERVAL_MS` | `5000` | Listener poll interval. |
| `SETTLEMENT_MAX_ATTEMPTS` | `3` | Retry attempts per event. |
| `SETTLEMENT_RETRY_BASE_MS` | `500` | Backoff base delay. |

## Commands

```bash
npm install
npm run prisma:generate # generate Prisma client
npm test # unit tests (no DB/chain needed)
npm run build # tsc -> dist/

# With a live PostgreSQL (DATABASE_URL set):
npx prisma migrate deploy # apply migrations
npm start # node dist/main, listens on PORT (4000)
```

The included migration (`prisma/migrations/00000000000000_init`) was generated
with `prisma migrate diff`; on a fresh database `prisma migrate deploy` creates
the `Invoice` and `SyncCursor` tables.

## Tests

Unit tests run fully offline (Prisma and the Soroban RPC are mocked):

- `common/retry.spec.ts` — backoff, attempt cap, last-error propagation.
- `settlement/settlement-event.parser.spec.ts` — event recognition & id extraction.
- `settlement/settlement.service.spec.ts` — atomic transition, idempotency, error cases.
- `settlement/settlement-sync.service.spec.ts` — cursor advancement, retry, fail-and-resume.
11 changes: 11 additions & 0 deletions backend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: 'src',
testRegex: '.*\\.spec\\.ts$',
transform: {
'^.+\\.ts$': ['ts-jest', { tsconfig: '<rootDir>/../tsconfig.json' }],
},
collectCoverageFrom: ['**/*.(t|j)s'],
testEnvironment: 'node',
};
Loading
Loading