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
8 changes: 4 additions & 4 deletions .github/workflows/ci-smoke-preprod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ jobs:
github.event.deployment.creator.login == 'railway-app[bot]' &&
contains(github.event.deployment.environment, 'preprod'))
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
# On deployment_status, check out the exact SHA that was deployed.
ref: ${{ github.event.deployment.sha || github.sha }}

- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22

- name: Check secrets configured
id: check-secrets
Expand All @@ -58,7 +58,7 @@ jobs:
run: npx tsx scripts/ci-smoke/run-route-chain.ts

- name: Upload smoke artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: always() && steps.check-secrets.outputs.configured == 'true'
with:
name: smoke-report
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
Expand All @@ -65,7 +65,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
Expand Down Expand Up @@ -93,6 +93,6 @@ jobs:
exit 1

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"
4 changes: 2 additions & 2 deletions .github/workflows/daily-balance-snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy-migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: '20'
node-version: 22
cache: 'npm'

- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/trpc-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: npm

- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22

- name: Install dependencies
run: npm ci
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ scripts/bot-ref/bot-wallet.json
.idea

# CI artifacts
ci-artifacts/
ci-artifacts/
.ci-dist/
45 changes: 43 additions & 2 deletions Dockerfile.ci
Original file line number Diff line number Diff line change
@@ -1,17 +1,58 @@
FROM node:20-alpine
# Debian (glibc), not alpine (musl): the @meshsdk/core-csl / whisky-evaluator
# WebAssembly modules don't resolve under musl during `next build`.
#
# Two stages:
# - `base` : deps + source. Used by the ci-runner service, which only runs
# tsx scripts and must NOT run `next build`.
# - `app` : base + a production `next build`. Used by the app service.
FROM node:22-slim AS base

# Install PostgreSQL client tools for readiness checks.
RUN apk add --no-cache postgresql-client
RUN apt-get update \
&& apt-get install -y --no-install-recommends postgresql-client \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

# @meshsdk/core-csl loads as a native ESM module whose whisky-evaluator WASM
# exports node can only resolve with --experimental-wasm-modules. Both the app
# (`next build` + `next start`) and the ci-runner (tsx scripts that import the
# Mesh SDK) need it, so set it on the shared base stage.
ENV NODE_OPTIONS=--experimental-wasm-modules

# Install dependencies first for better layer caching.
COPY package.json package-lock.json* ./
COPY prisma ./prisma
COPY prisma.config.ts ./
RUN npm ci

# Copy full source for containerized CI runs.
COPY . .

# Pre-bundle the CI CLI scripts with esbuild and run them with plain node.
# tsx's esbuild loader can't defer whisky-evaluator's .wasm imports to node's
# native --experimental-wasm-modules handling, so `tsx bootstrap.ts` fails to
# load @meshsdk/core-csl. Bundling (resolving @/ aliases, externalizing
# node_modules) lets `node --experimental-wasm-modules` load the WASM natively.
RUN npx --yes esbuild@0.25.10 \
scripts/ci/cli/bootstrap.ts \
scripts/ci/cli/wallet-status.ts \
scripts/ci/cli/route-chain.ts \
--bundle --platform=node --format=esm --packages=external \
--alias:@=./src --outdir=.ci-dist --out-extension:.js=.mjs

EXPOSE 3000

# --- Application image: production build served with `next start` ---------------
FROM base AS app

# Build the production app so the smoke runs `next start` (not `next dev`) and
# exercises the same output Vercel deploys. `next dev` mis-resolves the
# @meshsdk/core-csl / whisky-evaluator WASM path at runtime, 500-ing tx routes.
# Connection URLs are only needed at runtime (compose overrides these); the
# dummy DATABASE_URL keeps build-time module evaluation of the pg adapter happy.
# (NODE_OPTIONS=--experimental-wasm-modules is inherited from the base stage.)
ENV SKIP_ENV_VALIDATION=true
ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_URL=postgresql://build:build@localhost:5432/build
RUN npm run build
8 changes: 6 additions & 2 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
FROM node:20-alpine
# Debian (glibc), not alpine (musl): the @meshsdk/core-csl / whisky-evaluator
# WebAssembly modules don't resolve under musl.
FROM node:22-slim

# Install PostgreSQL client tools for health checks
RUN apk add --no-cache postgresql-client
RUN apt-get update \
&& apt-get install -y --no-install-recommends postgresql-client \
&& rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app
Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ A comprehensive, enterprise-grade multi-signature wallet solution built on Carda
- Secure multi-sig staking operations

### Collaboration
- Real-time Nostr-based chat
- Discord integration for notifications
- Signer verification via message signing
- Automated transaction alerts
Expand Down Expand Up @@ -188,11 +187,11 @@ graph TD
### Database Schema
```prisma
model User {
id String @id @default(cuid())
address String @unique
stakeAddress String @unique
nostrKey String @unique
discordId String @default("")
id String @id @default(cuid())
address String @unique
stakeAddress String @unique
nostrKey String? @unique
discordId String @default("")
}

model Wallet {
Expand Down
12 changes: 7 additions & 5 deletions docker-compose.ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ services:
build:
context: .
dockerfile: Dockerfile.ci
target: app
environment:
NODE_ENV: test
NEXT_TELEMETRY_DISABLED: "1"
Expand All @@ -41,8 +42,8 @@ services:
until pg_isready -h postgres -p 5432 -U postgres; do sleep 1; done &&
echo 'Running Prisma migrations...' &&
npx prisma migrate deploy || npx prisma db push &&
echo 'Starting application...' &&
npm run dev -- --hostname 0.0.0.0 --port 3000
echo 'Starting application (production build)...' &&
node_modules/.bin/next start --hostname 0.0.0.0 --port 3000
"
healthcheck:
test:
Expand All @@ -56,6 +57,7 @@ services:
build:
context: .
dockerfile: Dockerfile.ci
target: base
environment:
NODE_ENV: test
NEXT_TELEMETRY_DISABLED: "1"
Expand Down Expand Up @@ -91,9 +93,9 @@ services:
command: >
sh -c "
status=0;
npx --yes tsx scripts/ci/cli/bootstrap.ts || status=$$?;
if [ \"$$status\" -eq 0 ]; then npx --yes tsx scripts/ci/cli/wallet-status.ts || status=$$?; fi;
if [ \"$$status\" -eq 0 ]; then npx --yes tsx scripts/ci/cli/route-chain.ts || status=$$?; fi;
node .ci-dist/bootstrap.mjs || status=$$?;
if [ \"$$status\" -eq 0 ]; then node .ci-dist/wallet-status.mjs || status=$$?; fi;
if [ \"$$status\" -eq 0 ]; then node .ci-dist/route-chain.mjs || status=$$?; fi;
rm -f \"${CI_CONTEXT_PATH:-/tmp/ci-wallet-context.json}\";
exit \"$$status\"
"
Expand Down
52 changes: 26 additions & 26 deletions jest.config.mjs
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
/** @type {import('jest').Config} */
import { shared, ESM_TESTS, INTEGRATION_GLOB } from './jest.shared.mjs';

/**
* CJS project — runs under plain `jest` (no --experimental-vm-modules).
* Covers every test except the ESM-mode files (run via jest.esm.config.mjs) and
* the trpc/* database integration tests (run in their own workflow).
*
* @type {import('jest').Config}
*/
export default {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts', '.tsx'],
testEnvironment: 'node',
roots: ['<rootDir>/src'],
...shared,
testMatch: [
'**/__tests__/**/*.(test|spec).+(ts|tsx|js)',
'**/*.(test|spec).+(ts|tsx|js)'
'**/*.(test|spec).+(ts|tsx|js)',
],
testPathIgnorePatterns: [
'/node_modules/',
INTEGRATION_GLOB,
// Exclude the ESM-mode files (run separately). Anchored to /src/__tests__/
// so e.g. `pendingTransactions` does not also match trpc/pendingTransactions.
...ESM_TESTS.map((name) => `<rootDir>/src/__tests__/${name}\\.test\\.ts$`),
],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', {
useESM: true,
tsconfig: '<rootDir>/tsconfig.json',
}],
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
// libsodium-wrappers-sumo ships an .mjs that does `import "./libsodium-sumo.mjs"`,
// but that file lives in the separate `libsodium-sumo` package. Node resolves it
// via package.json exports; Jest's ESM resolver does not. Redirect.
'^\\./libsodium-sumo\\.mjs$': '<rootDir>/node_modules/libsodium-sumo/dist/modules-sumo-esm/libsodium-sumo.mjs',
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/pages/**',
'!src/components/**/*.tsx',
'!src/**/*.stories.{ts,tsx}',
'!src/__tests__/**',
// Some modules have import-time side effects (db.ts eagerly builds the
// Prisma client + pg adapter; the rate-limit module keeps a process-global
// store). jest force-loads collectCoverageFrom files that no test imported,
// and under v8 coverage that load pollutes global state and breaks tests
// which mock these modules (e.g. freeUtxos). Exclude them from collection —
// they are still covered when a test imports them directly.
'!src/server/**',
'!src/lib/security/**',
],
coverageProvider: 'v8',
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
'src/utils/stakingCertificates.ts': { lines: 90 },
'src/lib/tx-builders/buildDRepCertTx.ts': { lines: 90 },
},
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
testTimeout: 10000,
verbose: true,
};
13 changes: 13 additions & 0 deletions jest.esm.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { shared, ESM_TESTS } from './jest.shared.mjs';

/**
* ESM project — runs under `node --experimental-vm-modules`.
* Covers only the files that use `jest.unstable_mockModule()` / `import.meta` /
* ESM-only deps, which cannot run under the CJS project.
*
* @type {import('jest').Config}
*/
export default {
...shared,
testMatch: ESM_TESTS.map((name) => `<rootDir>/src/__tests__/${name}.test.ts`),
};
52 changes: 52 additions & 0 deletions jest.shared.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Shared jest settings for both module modes.
*
* This suite is mid-migration between two jest module systems:
* - CJS files use `jest.mock()` (CommonJS hoisting) and run under plain jest.
* - ESM files use `jest.unstable_mockModule()` / `import.meta` / ESM-only deps
* and run under `--experimental-vm-modules`.
* The two modes are mutually exclusive per file, so they run as separate jest
* invocations (see jest.config.mjs for CJS and jest.esm.config.mjs for ESM),
* both built from this shared base.
*/
export const ESM_TESTS = [
'apiSecurity',
'botBallotsUpsert',
'governanceActiveProposals',
'og',
'pendingTransactions',
'reviewSignersCardKey',
'signing',
'signTransaction',
];

// trpc/* are database integration tests; they run in their own workflow
// (trpc-integration-tests.yml) against a real Postgres, not in the unit run.
export const INTEGRATION_GLOB = '<rootDir>/src/__tests__/trpc/';

export const shared = {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts', '.tsx'],
testEnvironment: 'node',
roots: ['<rootDir>/src'],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', { useESM: true, tsconfig: '<rootDir>/tsconfig.json' }],
},
moduleNameMapper: {
// Stub `@/env` (ESM-only @t3-oss validator) before the general @/ alias.
'^@/env$': '<rootDir>/src/__tests__/__mocks__/env.cjs',
'^@/(.*)$': '<rootDir>/src/$1',
// libsodium-wrappers-sumo ships an .mjs that does `import "./libsodium-sumo.mjs"`,
// but that file lives in the separate `libsodium-sumo` package. Node resolves it
// via package.json exports; Jest's ESM resolver does not. Redirect.
'^\\./libsodium-sumo\\.mjs$': '<rootDir>/node_modules/libsodium-sumo/dist/modules-sumo-esm/libsodium-sumo.mjs',
'\\.(css|less|scss|sass)$': '<rootDir>/src/__tests__/__mocks__/styleMock.cjs',
},
transformIgnorePatterns: [
'/node_modules/(?!(superjson|copy-anything|is-what|@trpc|@meshsdk|@noble|@sidan-lab|nanoid|jose|uuid)/)',
],
setupFiles: ['<rootDir>/src/__tests__/setupEnv.cjs'],
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
testTimeout: 10000,
verbose: true,
};
Loading
Loading