Thank you for your interest in contributing to the Invoice Liquidity Network (ILN) frontend! This guide will help you get started with development, testing, and submitting contributions.
- Node.js: Version 18 or higher (recommended: Node.js 20 LTS)
- npm: Version 9 or higher
- Git: For version control
To help contributors find tasks aligned with their experience and available time, we triage issues by Complexity and Context/Familiarity requirements.
-
Good First Issue:
- Context Requirement: Low. A newcomer with no previous knowledge of our domain (Stellar/Soroban, invoice factoring, localized routing configurations) should be able to solve it using common web development skills.
- Self-Contained: The task has a clear start and end point, affects isolated files, and does not require complex integrations or cross-cutting structural modifications.
- Examples: Implementing helper scripts (such as
pnpm run clean), writing troubleshooting documentation, adding static content/badges, fixing localized stylesheets. - Label:
good-first-issue
-
Trivial Complexity:
- Context Requirement: Variable (often High). While the code changes themselves might be extremely small (e.g. changing 2 lines in a React context or smart contract call), it requires specific familiarity with the codebase, history, or integration layers to understand why the change is needed and how to do it safely.
- Examples: Tweaking a Freighter smart contract connection event listener, altering a specific Supabase permission or RLS script.
- Label:
complexity: trivial
For a curated list of candidate issues matching these criteria, see good-first-issue-candidates.md.
- Fork the ILN-Frontend repository
- Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/ILN-Frontend.git cd ILN-Frontend - Add the upstream remote:
git remote add upstream https://github.com/Invoice-Liquidity-Network/ILN-Frontend.git
Note for Fork Contributors: This repository uses a custom self-hosted GitHub Actions runner (namespace-profile-nursca) that is not available to forks. If you are working from a fork, you will need to modify workflow files to use GitHub-hosted runners (ubuntu-latest) instead. See docs/ci-cd.md for details.
npm installThe prepare script runs husky automatically, registering the hooks in .husky/.
| Hook | Trigger | Action |
|---|---|---|
pre-commit |
git commit |
Runs eslint --fix and prettier --write on staged files only |
pre-push |
git push |
Runs tsc --incremental to cache and catch type errors before pushing |
To optimize the contributor experience, we audited the performance of the Husky hooks:
pre-commit(npx lint-staged): Takes ~1.9s to run when no staged files need formatting, and typically under 5–10s for formatted staged edits.pre-push(tsc): Switching from a full typecheck (npx tsc --noEmit) to an incremental typecheck (npx tsc --incremental) improves performance significantly:- Cold Run (Full check / clean config): ~32.3 seconds.
- Warm Run (Incremental / local cache): ~8.4 seconds (a ~74% speedup).
The generated tsconfig.tsbuildinfo build cache file is ignored in .gitignore to keep git diffs clean.
If you genuinely need to bypass a hook in an emergency:
# Skip pre-commit only
git commit --no-verify -m "your message"
# Skip pre-push only
git push --no-verifyDo not make a habit of skipping — the same checks run in CI and will block your PR.
A .editorconfig file at the repository root gives every editor a consistent baseline (2-space indentation, LF line endings, UTF-8, final newline) before Prettier runs. Most editors support it natively or via a free plugin — see editorconfig.org for setup instructions.
Formatting rules live in .prettierrc.json. Files and directories excluded from formatting are listed in .prettierignore.
To format the entire codebase manually:
npx prettier --write .Copy the example environment file and configure it:
cp .env.example .env.localRequired environment variables (see README.md for full list):
NEXT_PUBLIC_CONTRACT_ID- Invoice factoring smart contract IDNEXT_PUBLIC_NETWORK_PASSPHRASE- Stellar network passphraseNEXT_PUBLIC_RPC_URL- Soroban RPC server endpointNEXT_PUBLIC_NETWORK_NAME- Network name (TESTNET/PUBLIC)NEXT_PUBLIC_STELLAR_NETWORK- Network type (testnet/public)- Token IDs for USDC, EURC, and XLM
NEXT_PUBLIC_SUPABASE_URL- Supabase database URLNEXT_PUBLIC_SUPABASE_ANON_KEY- Supabase anonymous keyRESEND_API_KEY- Resend email API key (server-side)CRON_SECRET- Secret for cron job security
NEXT_PUBLIC_NFT_ENABLED- Enable Invoice NFT metadata displayNEXT_PUBLIC_INSURANCE_POOL_ENABLED- Enable liquidity insurance poolingNEXT_PUBLIC_API_MOCKING- Enable MSW mocks for local development
- Install the Freighter wallet extension for your browser
- Create or import a Stellar account
- Switch to the appropriate network (Testnet for development)
If working on Testnet, fund your account using the Friendbot:
- Visit Stellar Testnet Friendbot
- Enter your Freighter wallet address
- Receive 10,000 XLM for testing
- Open Freighter extension
- Go to Settings
- Select "Testnet" network
- Ensure your account is active on the selected network
All shared UI components are documented in Storybook. Browse them locally:
npm run storybookA Storybook is also deployed to GitHub Pages on every merge to main — check the repo's Pages link for the latest published version.
npm run devOpen http://localhost:3000 in your browser.
Before pushing a branch or opening a PR, run:
pnpm run verifyThis runs the same checks as CI, in the same order, in a single command: lint → env:check → format:check → tsc --incremental → test. A passing pnpm run verify locally means the CI lint and tests jobs will pass too, so use it instead of running each check separately to avoid round-trips on avoidable CI failures.
If a build or test run succeeds in the GitHub Actions CI environment but fails locally on your machine, it is often due to stale build artifact caches, Next.js build caches, or outdated storybook/test caches.
To resolve this, run the clean script to clear out all generated build files and caches:
pnpm run cleanThis clears the following paths:
.next/(Next.js build cache).turbo/(Turborepo execution cache)storybook-static/(Storybook static build)coverage/(Vitest coverage reports)test-results/&playwright-report/(Playwright E2E test artifacts).lighthouseci/(Lighthouse audit report caches)tsconfig.tsbuildinfo(TypeScript incremental compilation info)
After cleaning, run a fresh install and verify:
pnpm install
pnpm run verifyFor contributors who prefer using make, a top-level Makefile is available mirroring standard pnpm tasks:
| Target | Executed Command | Purpose |
|---|---|---|
make install |
pnpm install |
Install dependencies |
make dev |
pnpm dev |
Start development server |
make build |
pnpm build |
Build production bundle |
make test |
pnpm test |
Run Vitest unit tests |
make lint |
pnpm lint |
Run ESLint check |
make format |
pnpm format |
Run Prettier formatter |
make verify |
pnpm verify |
Run full verification suite |
To quickly create a new React component along with its matching Storybook story and Vitest test stub following project conventions:
pnpm scaffold:component <ComponentName>
# Or for nested components:
pnpm scaffold:component ui/CustomCardThis command generates:
- Component file:
src/components/<ComponentName>.tsx - Storybook file:
src/components/<ComponentName>.stories.tsx - Test stub file:
src/components/__tests__/<ComponentName>.test.tsx(or inside the target subdirectory)
We use ESLint and Prettier to maintain consistent code quality.
# Check for linting errors
npm run lint
# Auto-fix linting errors
npm run lint:fix# Format all files
npm run format
# Check formatting without modifying files
npm run format:checkWe recommend using Husky for pre-commit hooks (optional but recommended):
npm install --save-dev husky lint-staged
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"Add to package.json:
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md}": [
"prettier --write"
]
}# Run all unit tests
npm test
# Run tests in watch mode
npm test -- --watch
# Run tests with coverage
npm test -- --coverage
# Update snapshots after intentional UI changes
npm test -- --update-snapshotsThis codebase currently has two coexisting test location conventions. Both are intentional and supported - use the one that matches what you're testing:
- Colocated
__tests__/- next to the module under test, e.g.src/hooks/__tests__/useFoo.test.tsforsrc/hooks/useFoo.ts, orsrc/components/governance/__tests__/Bar.test.tsxforsrc/components/governance/Bar.tsx. This is the default for unit tests of a single hook, util, or component:src/utils/__tests__,src/lib/__tests__,src/hooks/__tests__, and the varioussrc/components/**/__tests__folders all follow this pattern, as doapp/offline/__tests__andapp/pay/[id]/__tests__for route-level components. - Centralized top-level
__tests__/- for suites that don't map 1:1 to a single source file: cross-page or integration-style tests, and grouped cross-cutting concerns in a named subdirectory, e.g.__tests__/contract/(on-chain/contract integration tests),__tests__/accessibility/(per-page a11y audits,*.a11y.test.tsx), and__tests__/error-boundaries/. Fixtures shared across these live in__tests__/fixtures/.
When adding a new test, prefer colocation (1) if it exercises a single
hook/util/component in isolation. Use the centralized directory (2) if it's an
integration suite spanning multiple modules/pages, or belongs to one of the
existing grouped concerns above - add a new named subdirectory under __tests__/
rather than a new flat top-level file if you're starting a new cross-cutting
concern.
Note: a number of component tests still live as flat files directly under
__tests__/ (not colocated) from before this convention was documented. Those are
not being mass-moved as part of documenting this convention - this section
only governs where new tests should go. Bulk migration to colocation is a
candidate for a future dedicated issue.
- Automated Detection: A scheduled CI workflow (
flaky-test-detection.yml) runs the full test suite 3× sequentially on a weekly schedule (every Sunday at 03:00 UTC) to identify intermittent test failures without burdening per-PR CI run times. - Quarantining a Flaky Test:
- Open a GitHub Issue titled
flaky: <Test Description / Suite Name>detailing the failure log and frequency. - Mark the flaky test using
.skip(e.g.it.skip(...)ordescribe.skip(...)) in code. - Include a comment above the
.skipreferencing the tracking issue URL:// Quarantined due to flakiness - see https://github.com/Invoice-Liquidity-Network/ILN-Frontend/issues/<issue_number> it.skip('handles dynamic timer updates without race conditions', () => { ... });
- Fix the underlying timing or async race condition in a follow-up PR and remove
.skip.
- Open a GitHub Issue titled
# Run all E2E tests
npm run test:e2e
# Run E2E tests in headed mode (for debugging)
npm run test:e2e -- --headed
# Run specific test file
npm run test:e2e -- invoice-submission.spec.ts# Start Storybook locally
npm run storybook
# Build Storybook
npm run build-storybook
# Run Chromatic visual tests
npm run chromaticThis repository uses Conventional Commits to power changelog generation via git-cliff.
- Commit messages should follow the format:
<type>(<scope>): <short summary>. - Use the types:
feat,fix,docs,style,refactor,perf,test,build,ci,chore,revert. - Example:
chore: add CHANGELOG and git-cliff automation for frontend repo - After adding release-worthy commits, update the changelog with:
npm run generate:changelog
To maintain consistency and enable automated tooling, all branches should follow the Conventional Commits prefix convention:
feat/- New featuresfix/- Bug fixesdocs/- Documentation changeschore/- Maintenance tasks (dependencies, tooling, etc.)perf/- Performance improvementstest/- Test additions or modificationsci/- CI/CD configuration changesrefactor/- Code refactoring (no functional changes)
Examples:
feat/add-invoice-submission-formfix/stellar-wallet-connectiondocs/update-contributing-guidechore/upgrade-dependencies
This convention aligns with our commit message format and helps with changelog generation.
-
Code Quality:
- Run
pnpm run verify(lint, env:check, format:check, tsc --noEmit, test) and ensure it passes — this mirrors CI exactly - Run
npm run lint:fixto fix all linting errors - Run
npm run formatto ensure consistent formatting - Ensure zero ESLint warnings
- Run
-
Testing:
- Run
npm testand ensure all tests pass - Run
npm run test:e2efor critical user flows - Add tests for new features or bug fixes
- Maintain test coverage above thresholds (90% lines, 90% functions, 80% branches)
- Run
-
Visual Changes:
- If your PR includes UI changes, run
npm run storybook - Ensure Storybook stories are updated or added for new components
- Chromatic will automatically run visual regression tests on your PR
- If your PR includes UI changes, run
-
Documentation:
- Update relevant documentation (README, DESIGN.md, architecture docs)
- Add comments for complex logic
- Update TypeScript types if needed
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Unit tests pass
- [ ] E2E tests pass
- [ ] Manual testing completed
- [ ] Visual regression tests pass
## Screenshots (if applicable)
Add screenshots for UI changes
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Comments added for complex logic
- [ ] Documentation updated
- [ ] No new warnings generated
- [ ] All tests passingILN supports multiple languages using i18next. All user-facing strings must be externalized.
-
Add strings to translation files:
- English:
public/locales/en/translation.json - Spanish:
public/locales/es/translation.json - Add new locales by creating corresponding directories
- English:
-
Use translations in components:
import { useTranslation } from 'react-i18next'; function MyComponent() { const { t } = useTranslation(); return <h1>{t('common.submit')}</h1>; }
-
Locale-aware formatting: We provide a custom hook for locale-aware formatting:
import { useLocaleFormatting } from '@/hooks/useLocaleFormatting'; function MyComponent() { const { currency, date, percentage, tokenAmount } = useLocaleFormatting(); // Format currency const formatted = currency(1000, 'USD'); // "$1,000.00" or "1.000,00 €" // Format date const formattedDate = date(new Date(), { dateStyle: 'medium' }); // Format percentage const formattedPercent = percentage(0.05, 2); // "5.00%" // Format token amount const formattedToken = tokenAmount(1000000000n, 7, 'USDC'); }
Or use the utility functions directly:
import { formatCurrency, formatDate } from '@/lib/formatting'; // Numbers (amounts, percentages) const formatted = formatCurrency(1000, 'USD', 'en-US'); // Dates const formattedDate = formatDate(new Date(), { dateStyle: 'medium' }, 'en-US');
-
Create locale directory:
public/locales/[locale]/ -
Copy
translation.jsonfrom English locale -
Translate all strings
-
Update
src/i18n.ts:import [locale] from "../public/locales/[locale]/translation.json"; const resources = { en: { translation: en }, es: { translation: es }, [locale]: { translation: [locale] }, }; supportedLngs: ["en", "es", "[locale]"],
The i18n configuration is in src/i18n.ts:
- Uses
i18next-browser-languagedetectorfor automatic language detection - Persists language preference in localStorage
- Falls back to English if translation is missing
- Supports English (en) and Spanish (es) out of the box
Tests use Mock Service Worker (MSW) to mock network calls at the request boundary instead of mocking individual app functions. This makes tests more realistic and maintainable.
MSW is configured for both Node (Vitest) and browser (Playwright) environments:
- Server setup:
src/mocks/server.ts - Browser setup:
src/mocks/browser.ts - Handlers:
src/mocks/handlers.ts - Fixtures:
src/mocks/fixtures/
-
Add or update fixtures in
src/mocks/fixtures/:// src/mocks/fixtures/contract.ts export const myNewFixture = { // realistic API response data };
-
Add request handler in
src/mocks/handlers.ts:import { http, HttpResponse } from 'msw'; import { myNewFixture } from './fixtures/contract'; export const handlers = [ http.get('https://api.example.com/endpoint', () => { return HttpResponse.json(myNewFixture); }), ];
-
Use in tests:
import { server } from '@/mocks/server'; describe('MyComponent', () => { it('should handle API response', () => { server.use( http.get('https://api.example.com/endpoint', () => { return HttpResponse.json({ custom: 'response' }); }) ); // test logic }); });
Current MSW handlers cover:
- Horizon account/balance endpoints
- Horizon transaction endpoints
- Friendbot faucet endpoint
- CoinGecko price endpoint
- Soroban RPC contract calls
- Internal API endpoints (leaderboard, notifications)
When migrating existing tests from function mocks to MSW:
- Identify mocked functions (e.g.,
jest.fn(),vi.fn()) - Replace with MSW handlers that intercept the actual network request
- Remove function mock imports and setup
- Verify tests still pass with realistic network responses
Example migration:
// Before (function mock)
vi.mock('@/lib/horizonClient', () => ({
fetchNativeXlmBalance: vi.fn().mockResolvedValue(1000),
}));
// After (MSW handler)
import { server } from '@/mocks/server';
server.use(
http.get('https://horizon-testnet.stellar.org/accounts/:accountId', () => {
return HttpResponse.json({
balances: [{ asset_type: 'native', balance: '1000' }],
});
})
);This project uses Chromatic for visual regression testing to catch unintended UI changes before they reach production.
-
Install dependencies:
npm install
-
Set up Chromatic project:
- Create an account at chromatic.com
- Link your GitHub repository
- Get your project token from the Chromatic dashboard
- Add the token to your environment:
CHROMATIC_PROJECT_TOKEN=your_token_here
# Start Storybook locally
npm run storybook
# Build Storybook for production
npm run build-storybook
# Run Chromatic visual tests
npm run chromaticVisual regression tests run automatically on:
- Pull requests from maintainers (with access to repository secrets)
- Pushes to main branch
- Manual workflow dispatch
Note for first-time contributors: Visual regression tests are skipped for fork PRs because they require the CHROMATIC_PROJECT_TOKEN secret, which is not available to forks. When the Chromatic check is skipped, you'll see a clear notice in the PR checks explaining why. Your code will still be tested by other CI checks (linting, unit tests, E2E tests). A maintainer will review visual changes when merging your PR.
-
Review Changes:
- Chromatic will comment on your PR with a link to review changes
- Click the link to see before/after comparisons
- Review each component change carefully
-
Approve Intentional Changes:
- If changes are intentional (new features, design updates):
- Click "Accept" for each intended change in Chromatic
- Add a comment explaining the change
- If changes are unintentional:
- Click "Deny" and fix the issue in your code
- Push new commits to update the visual tests
- If changes are intentional (new features, design updates):
-
Baseline Updates:
- Approved changes become the new baseline
- Future tests will compare against these new baselines
- Only maintainers can approve changes on the main branch
-
Component Stories:
- Write comprehensive stories covering all component states
- Include edge cases (loading, error, empty states)
- Test different prop combinations
- Use realistic data in stories
-
Responsive Testing:
- Test components at different viewport sizes
- Include mobile, tablet, and desktop breakpoints
- Use Storybook's viewport addon for consistent testing
-
Accessibility:
- All stories are automatically tested with axe-core
- Fix accessibility violations before merging
- Use semantic HTML and proper ARIA attributes
-
Performance:
- Keep stories lightweight and focused
- Avoid heavy computations in story renders
- Use mock data instead of real API calls
src/components/
├── Button/
│ ├── Button.tsx
│ ├── Button.stories.tsx
│ └── Button.test.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from './ComponentName';
const meta: Meta<typeof ComponentName> = {
title: 'Components/ComponentName',
component: ComponentName,
parameters: {
layout: 'centered', // or 'padded', 'fullscreen'
},
tags: ['autodocs'],
argTypes: {
// Define controls for props
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
// Default props
},
};
export const Variant: Story = {
args: {
// Variant props
},
};-
Button- All variants, sizes, states -
InvoiceStatusBadge- All status types -
RiskBadge- All risk levels -
DataTable- Loading, empty, populated states -
TokenSelector- All token types, error states
-
InvoiceTable- Different data sets, filters -
LPPortfolio- Various portfolio states -
NotificationBell- Read/unread states -
Modalcomponents - Open/closed states -
Formcomponents - Valid/invalid states
-
Flaky Tests:
- Use
chromatic --exit-zero-on-changesfor non-blocking tests - Add delays for animations:
parameters: { chromatic: { delay: 300 } } - Disable animations in test environment
- Use
-
Large Diffs:
- Check for font loading issues
- Ensure consistent test environment
- Use fixed dimensions for dynamic content
-
Missing Baselines:
- Run
npm run chromaticon main branch first - Ensure all stories are properly exported
- Check Storybook build for errors
- Run
- Check the Chromatic documentation
- Review existing stories for patterns
- Ask in the team Slack channel for guidance
- Create an issue for persistent problems
- Review and approve visual changes weekly
- Update baselines after major design changes
- Archive old unused stories
- Monitor Chromatic usage and costs
- Test Storybook updates in a separate branch
- Regenerate all baselines after major updates
- Update this documentation as needed
If you need help:
- Check the consolidated troubleshooting guide: docs/troubleshooting.md for local environment setup issues, Freighter, Supabase or Resend gotchas.
- Check existing GitHub Issues
- Review the architecture documentation
- Read the design system guide
- Join community discussions (link to Discord/Slack if available)
This project uses a CODEOWNERS file to automatically request reviews from maintainers based on which files are changed in a pull request.
The following areas have designated code owners to ensure consistent review and maintainability:
- Core React hooks (
src/hooks/): Wallet integration, contract interactions, and data fetching logic require deep understanding of the application's reactive architecture. - React Context providers (
src/context/): Global state management and wallet context are critical infrastructure that affect the entire application. - API routes (
app/api/): Backend endpoints handle authentication, data APIs, and server-side logic that require security and performance considerations. - GitHub Actions workflows (
.github/workflows/): CI/CD pipelines, testing, and deployment configurations need careful review to prevent breaking changes. - Governance utilities (
src/utils/governance.ts): Voting and proposal management logic is domain-specific and requires governance expertise. - Contract layer (
src/lib/soroban.ts,src/lib/horizon.ts,src/lib/indexer-websocket.ts): Stellar SDK integration, transaction signing, and indexer connections are critical blockchain infrastructure. - Core constants and configuration (
src/constants.ts): Central configuration affects the entire application and requires careful review. - Type definitions (
src/types/): TypeScript types define the contract for the entire codebase. - Documentation (
docs/): Documentation changes require review to ensure accuracy and consistency.
When you open a PR, GitHub will automatically suggest reviewers based on the files you've changed. This helps ensure that:
- Changes to critical infrastructure get appropriate review
- Domain experts review changes in their areas of expertise
- Review turnaround time is improved by routing to the right people
- Knowledge is distributed across the team
If you become a regular contributor to a specific area of the codebase, you can request to be added as a code owner. Contact a maintainer to discuss this.
Please be respectful and constructive in all interactions. We aim to create a welcoming environment for all contributors.
ILN uses NEXT_PUBLIC_*_ENABLED environment variable flags to gate features that are not yet live. Flags are a short-term tool — they must not accumulate indefinitely.
| Flag | Default | Purpose |
|---|---|---|
NEXT_PUBLIC_INSURANCE_POOL_ENABLED |
false |
Liquidity insurance pooling panel |
NEXT_PUBLIC_NFT_ENABLED |
false |
Soroban Invoice NFT metadata displays |
NEXT_PUBLIC_ORACLE_ENABLED |
false |
Oracle verification badges |
[Draft] → [Gated / false] → [Gated / true (canary)] → [Shipped → removed]
- Gated / false — Feature is in development. All code paths are wrapped with
if (env.NEXT_PUBLIC_*_ENABLED). The flag defaults tofalseinsrc/lib/env.tsand is listed in.env.local.example. - Gated / true (canary) — Feature is complete and under observation. The flag is set to
truein production environment variables but the conditional code paths remain. - Shipped — Feature has been stable for at least one full sprint. The flag must be removed. See the removal checklist below.
- Add the env var to
src/lib/env.tsusingbooleanEnv('NEXT_PUBLIC_MY_FEATURE_ENABLED'). - Add an entry to
.env.local.examplewith valuefalseand a comment describing the feature. - Document it in the table above in this section.
- Open an issue to track the flag's removal, linked to the shipping milestone.
When a feature ships and the flag is no longer needed:
- Remove the
NEXT_PUBLIC_*_ENABLEDentry fromsrc/lib/env.ts. - Remove the entry from
.env.local.example. - Delete all
if (env.NEXT_PUBLIC_*_ENABLED)conditional branches and theirelsepaths (keep only the enabled code). - Remove the flag from the table in this section of
CONTRIBUTING.md. - Update
README.mdif the flag appeared in the Environment Variables Reference table. - Run the audit script to confirm no stale references remain:
pnpm exec tsx scripts/audit-feature-flags.ts
The repo includes scripts/audit-feature-flags.ts, which scans the codebase and reports:
- Which flags exist and where they are referenced.
- Flags with zero code references (candidates for cleanup).
- Flags whose default is
true(candidates for full removal).
This script runs as an informational, non-blocking CI step on every PR that touches flag-gated code (see .github/workflows/feature-flag-audit.yml). The report appears in the GitHub Actions job summary.