diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20f0873..a4b3abf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + cache: npm - run: npm audit --audit-level=high commitlint: @@ -52,7 +53,7 @@ jobs: cache: npm - name: Install dependencies - run: npm install + run: npm ci # Lint every commit in the PR from the merge base to HEAD. - name: Lint commit messages @@ -93,20 +94,14 @@ jobs: steps: - uses: actions/checkout@v4 + # ── Node / npm cache (#130) ────────────────────────────────────────── - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm run lint - - run: npm run typecheck - - run: npm run build - - run: npm test -- --coverage - - run: npm run test:e2e - node-version: 20 cache: npm - name: Install dependencies - run: npm install + run: npm ci # ── Prisma ────────────────────────────────────────────────────────────── - name: Validate Prisma schema @@ -135,3 +130,21 @@ jobs: - name: E2E tests run: npm run test:e2e + + # ── Docker build verification (#131) ────────────────────────────────────── + # Ensures every push that changes application code doesn't silently break + # the Dockerfile. Image publishing to a registry can be wired up separately + # once registry credentials are in place. + docker: + name: Docker – build verification + runs-on: ubuntu-latest + needs: backend + steps: + - uses: actions/checkout@v4 + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: false + tags: vortex-backend:ci diff --git a/README.md b/README.md index 99d52f7..958575e 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,29 @@ flags to override them. --- +## Supported chains + +`SupportedChain` in `src/intents/intents.types.ts` lists seven chains. +The table below clarifies which are **live** (real integration exists today) +versus **planned** (schema/token data in place, on-chain settlement pending). + +| Chain | Status | Notes | +|-------|--------|-------| +| **Stellar** | ✅ Live | Soroban RPC reads (`/api/v1/chain/*`), signing service, settlement design in progress | +| Ethereum | 🔲 Planned | Token registry populated; on-chain integration not yet implemented | +| Base | 🔲 Planned | Token registry populated; on-chain integration not yet implemented | +| Polygon | 🔲 Planned | Token registry populated; on-chain integration not yet implemented | +| Arbitrum | 🔲 Planned | Token registry populated; on-chain integration not yet implemented | +| Optimism | 🔲 Planned | Token registry populated; on-chain integration not yet implemented | +| Avalanche | 🔲 Planned | Token registry populated; on-chain integration not yet implemented | + +> **Contributor note:** EVM chains are accepted in the intent DTO and stored +> in-memory, but no on-chain settlement or bridging logic is wired up yet. +> See [`docs/architecture/onchain-settlement.md`](./docs/architecture/onchain-settlement.md) +> for the target design. + +--- + ## Roadmap - [x] **Soroban RPC reads** — health/ledger/network/account lookups via `/api/v1/chain/*` diff --git a/src/intents/dto/create-intent.dto.ts b/src/intents/dto/create-intent.dto.ts index f7c7f5c..ea8a9c3 100644 --- a/src/intents/dto/create-intent.dto.ts +++ b/src/intents/dto/create-intent.dto.ts @@ -1,19 +1,9 @@ import { IsIn, IsInt, IsOptional, IsString, Matches, Max, Min, MinLength } from "class-validator"; import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { SupportedChain } from "../intents.types"; +import { SUPPORTED_CHAINS, SupportedChain } from "../intents.types"; import { IsValidAddress } from "../../common/validators/is-valid-address.validator"; import { IsValidDeadline } from "../../common/validators/deadline.validator"; -const SUPPORTED_CHAINS: SupportedChain[] = [ - "stellar", - "ethereum", - "base", - "polygon", - "arbitrum", - "optimism", - "avalanche", -]; - export class CreateIntentDto { @ApiProperty({ description: "Stellar address of the user creating the intent" }) @IsString() diff --git a/src/intents/intents.types.ts b/src/intents/intents.types.ts index b16cfe1..5c5c79b 100644 --- a/src/intents/intents.types.ts +++ b/src/intents/intents.types.ts @@ -1,11 +1,20 @@ -export type SupportedChain = - | "stellar" - | "ethereum" - | "base" - | "polygon" - | "arbitrum" - | "optimism" - | "avalanche"; +/** + * Single source of truth for every chain the protocol recognises. + * `SupportedChain` is derived from this tuple so all three consumers + * (intents.types.ts, create-intent.dto.ts, tokens.data.ts) stay in sync + * automatically — see issue #128. + */ +export const SUPPORTED_CHAINS = [ + "stellar", + "ethereum", + "base", + "polygon", + "arbitrum", + "optimism", + "avalanche", +] as const; + +export type SupportedChain = (typeof SUPPORTED_CHAINS)[number]; /** * A single entry in the append-only audit log for an intent. diff --git a/src/intents/supported-chains-sync.spec.ts b/src/intents/supported-chains-sync.spec.ts new file mode 100644 index 0000000..913280c --- /dev/null +++ b/src/intents/supported-chains-sync.spec.ts @@ -0,0 +1,42 @@ +/** + * Regression guard for issue #128. + * + * Ensures that: + * 1. CreateIntentDto's @IsIn validator uses the same canonical list as + * `SupportedChain` (guaranteed structurally after the refactor — this + * test documents the contract). + * 2. Every EVM chain declared in SUPPORTED_CHAINS has a corresponding entry + * in tokens.data.ts so the token registry never silently lags behind. + */ +import { SUPPORTED_CHAINS } from "./intents.types"; +import { SUPPORTED_TOKENS } from "../tokens/tokens.data"; + +describe("SUPPORTED_CHAINS sync guard (#128)", () => { + it("SUPPORTED_CHAINS contains at least 'stellar' and at least one EVM chain", () => { + expect(SUPPORTED_CHAINS).toContain("stellar"); + const evmChains = SUPPORTED_CHAINS.filter((c) => c !== "stellar"); + expect(evmChains.length).toBeGreaterThan(0); + }); + + it("every EVM chain in SUPPORTED_CHAINS has token entries in tokens.data.ts", () => { + const evmChains = SUPPORTED_CHAINS.filter((c) => c !== "stellar"); + const missingChains: string[] = []; + + for (const chain of evmChains) { + const tokens = SUPPORTED_TOKENS[chain]; + if (!tokens || tokens.length === 0) { + missingChains.push(chain); + } + } + + expect(missingChains).toEqual([]); + }); + + it("tokens.data.ts has no extra EVM chains that are not listed in SUPPORTED_CHAINS", () => { + const tokenChains = Object.keys(SUPPORTED_TOKENS); + const unrecognised = tokenChains.filter( + (c) => !(SUPPORTED_CHAINS as readonly string[]).includes(c), + ); + expect(unrecognised).toEqual([]); + }); +});