Skip to content
Open
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
23 changes: 22 additions & 1 deletion .github/codecov.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
coverage:
precision: 2
round: down
range: "60...90"
status:
project:
backend:
target: 60%
# Fail the PR if backend coverage drops by more than 1%.
threshold: 1%
flags:
- backend
contracts:
target: 70%
threshold: 1%
flags:
- contracts
frontend:
target: auto
# Fail the PR if frontend coverage drops by more than 1%.
threshold: 1%
flags:
- frontend
patch:
default:
target: 60%

flags:
backend:
paths:
- backend/
carryforward: true
frontend:
paths:
- frontend/
carryforward: true
contracts:
carryforward: true

comment:
layout: "diff, flags, files"
# Per-file coverage change table on every PR.
layout: "reach, diff, flags, files"
behavior: default
require_changes: false
35 changes: 33 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ jobs:
if: >-
github.event_name == 'push' ||
needs.changes.outputs.frontend == 'true'
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -52,7 +55,7 @@ jobs:
cache-dependency-path: package-lock.json

- name: Install dependencies
run: npm ci --include=optional
run: npm install --include=optional

- name: Lint
run: npm run lint
Expand All @@ -62,6 +65,19 @@ jobs:
run: npm run test:coverage
working-directory: frontend

- name: Post frontend coverage summary on PR
if: github.event_name == 'pull_request' && !cancelled()
# Fork PRs get a read-only token; never fail the build over the comment.
continue-on-error: true
uses: davelosert/vitest-coverage-report-action@v2
with:
working-directory: frontend
name: Frontend coverage
json-summary-path: coverage/coverage-summary.json
json-final-path: coverage/coverage-final.json
file-coverage-mode: changes
vite-config-path: vitest.config.ts

- name: Upload frontend coverage to Codecov
uses: codecov/codecov-action@v5
with:
Expand All @@ -88,6 +104,9 @@ jobs:
if: >-
github.event_name == 'push' ||
needs.changes.outputs.backend == 'true'
permissions:
contents: read
pull-requests: write
services:
postgres:
image: postgres:16-alpine@sha256:e013e867e712fec275706a6c51c966f0bb0c93cfa8f51000f85a15f9865a28cb
Expand Down Expand Up @@ -115,7 +134,7 @@ jobs:
cache-dependency-path: package-lock.json

- name: Install dependencies
run: npm ci --include=optional
run: npm install --include=optional

- name: Setup Database
run: |
Expand Down Expand Up @@ -147,6 +166,18 @@ jobs:
DATABASE_URL: postgresql://postgres:password@127.0.0.1:5432/flowfi_test
NODE_ENV: test

- name: Post backend coverage summary on PR
if: github.event_name == 'pull_request' && !cancelled()
continue-on-error: true
uses: davelosert/vitest-coverage-report-action@v2
with:
working-directory: backend
name: Backend coverage
json-summary-path: coverage/coverage-summary.json
json-final-path: coverage/coverage-final.json
file-coverage-mode: changes
vite-config-path: vitest.config.ts

- name: Upload backend coverage to Codecov
uses: codecov/codecov-action@v5
with:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,4 @@ This project follows the [all-contributors](https://github.com/all-contributors/
## License

MIT.
# ci fix
11 changes: 5 additions & 6 deletions backend/prisma/migrations/20260221132622_init/migration.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ CREATE TABLE "User" (
"updatedAt" TIMESTAMP(3) NOT NULL
);

-- The referenced columns must be unique before PostgreSQL creates their foreign keys.
CREATE UNIQUE INDEX "User_publicKey_key" ON "User"("publicKey");

-- CreateTable
CREATE TABLE "Stream" (
"id" TEXT NOT NULL PRIMARY KEY,
Expand All @@ -25,6 +28,8 @@ CREATE TABLE "Stream" (
CONSTRAINT "Stream_recipient_fkey" FOREIGN KEY ("recipient") REFERENCES "User" ("publicKey") ON DELETE RESTRICT ON UPDATE CASCADE
);

CREATE UNIQUE INDEX "Stream_streamId_key" ON "Stream"("streamId");

-- CreateTable
CREATE TABLE "StreamEvent" (
"id" TEXT NOT NULL PRIMARY KEY,
Expand All @@ -39,15 +44,9 @@ CREATE TABLE "StreamEvent" (
CONSTRAINT "StreamEvent_streamId_fkey" FOREIGN KEY ("streamId") REFERENCES "Stream" ("streamId") ON DELETE RESTRICT ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "User_publicKey_key" ON "User"("publicKey");

-- CreateIndex
CREATE INDEX "User_publicKey_idx" ON "User"("publicKey");

-- CreateIndex
CREATE UNIQUE INDEX "Stream_streamId_key" ON "Stream"("streamId");

-- CreateIndex
CREATE INDEX "Stream_sender_idx" ON "Stream"("sender");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
-- AlterTable
ALTER TABLE "Stream" ADD COLUMN IF NOT EXISTS "endTime" BIGINT;

ALTER TABLE "Stream" ALTER COLUMN "startTime" SET DATA TYPE BIGINT,
ALTER COLUMN "lastUpdateTime" SET DATA TYPE BIGINT,
ALTER COLUMN "endTime" SET DATA TYPE BIGINT,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
-- An earlier, superseded migration (20260830000000) created a table of the
-- same name with a different shape. Drop it first so this migration is
-- idempotent whether or not that older table exists.
DROP TABLE IF EXISTS "IndexerDeadLetterEvent";

-- CreateTable
CREATE TABLE "IndexerDeadLetterEvent" (
"id" TEXT NOT NULL,
Expand Down
1 change: 1 addition & 0 deletions backend/src/lib/pg-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export function publishPoolMetrics(pool: pg.Pool): void {
dbPoolMaxConnections.set(pool.options?.max ?? 0);
}


/**
* Reduce a SQL statement to a low-cardinality operation label.
*
Expand Down
2 changes: 2 additions & 0 deletions backend/src/lib/prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ if (!globalForPrisma.pool) {
globalForPrisma.pool = createPgPool();
}

// Standalone backend installs can resolve a second @types/pg copy under the
// adapter. Both declarations describe the same runtime Pool instance.
const log =
process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn'] as const
Expand Down
10 changes: 10 additions & 0 deletions backend/src/lib/rpc-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,13 @@ export class RpcPool {

export const rpcPool = new RpcPool();
rpcPool.startHealthProbes();

/**
* True when at least one configured Soroban RPC endpoint has an operational
* (non-OPEN) circuit. Synchronous on purpose: a liveness probe must not spend
* an RPC round-trip on its verdict — the background health probes started
* above keep this signal current (~30 s granularity).
*/
export function rpcPoolHealthy(): boolean {
return rpcPool.getHealth().some((endpoint) => endpoint.state !== 'OPEN');
}
6 changes: 4 additions & 2 deletions backend/src/middleware/admin-rate-limiter.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ export const adminRateLimiter = rateLimit({
status: 429,
},
keyGenerator: (req: Request): string => {
// Use x-forwarded-for or remote address as key. ipKeyGenerator normalizes
// IPv6 addresses to a subnet (required by express-rate-limit v8).
// Prefer the left-most proxy hop; fall back to the subnet-scoped request
// IP (ipKeyGenerator buckets IPv6 clients by /64 so a single user cannot
// rotate addresses to bypass the limit — express-rate-limit's
// ERR_ERL_KEY_GEN_IPV6 requirement).
const forwarded = req.headers['x-forwarded-for'];
const ip =
typeof forwarded === 'string'
Expand Down
114 changes: 0 additions & 114 deletions backend/src/services/indexer.service.ts

This file was deleted.

23 changes: 16 additions & 7 deletions backend/src/services/sorobanService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,13 @@ export async function submitContractCall(method: string, args: xdr.ScVal[], send
}

/**
* Poll a submitted transaction until it reaches a final on-chain state.
*
* `sendTransaction` only queues the transaction; without this wait a caller
* could immediately read chain state and see a pre-transaction view. Returns
* the final successful transaction response, or throws when the transaction
* fails on-chain or the confirmation window lapses.
*
* Poll Soroban RPC getTransaction until the transaction reaches a terminal
* status (SUCCESS or FAILED) or until the bounded timeout expires.
*/
Expand Down Expand Up @@ -898,25 +905,27 @@ function decodeSimulatedReturn(result: rpc.Api.SimulateTransactionSuccessRespons
if (!retval) return '';

try {
// SDK 17 models ScVal as a discriminated union keyed on `type`; each
// variant exposes its arm payload via the `value` getter.
switch (retval.type) {
case 'scvI128':
return decodeI128(retval);
case 'scvU64':
return retval.u64.toString();
return retval.value.toString();
case 'scvU32':
return retval.u32.toString();
return retval.value.toString();
case 'scvI64':
return retval.i64.toString();
return retval.value.toString();
case 'scvU128': {
const parts = retval.u128;
const hi = BigInt.asUintN(64, parts.hi);
const lo = BigInt.asUintN(64, parts.lo);
const parts = retval.value;
const hi = BigInt.asUintN(64, BigInt(parts.hi.toString()));
const lo = BigInt.asUintN(64, BigInt(parts.lo.toString()));
return ((hi << 64n) | lo).toString();
}
default:
// Non-numeric returns (addresses, maps, void markers) are surfaced as
// base64 XDR so the client can decode them with the SDK if it needs to.
return Buffer.from(retval.toXDR()).toString('base64');
return Buffer.from(retval.toXdr()).toString('base64');
}
} catch {
return '';
Expand Down
1 change: 0 additions & 1 deletion backend/src/workers/soroban-event-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {

/** Ledger rollbacks above this ceiling are escalated to dead-letter triage. */
const REORG_ALERT_THRESHOLD_DEFAULT = 5;

// ─── XDR Decoding Helpers ────────────────────────────────────────────────────

/** Use the ledger close time, not worker processing time, for historical state changes. */
Expand Down
Loading
Loading