Thanks for your interest in contributing. This guide covers everything you need to get the project running locally and submit quality changes.
- Project Structure
- Prerequisites
- Local Setup
- Running the Project
- Environment Variables
- Database Migrations
- Testing
- Code Style
- Branching and Commits
- Submitting a Pull Request
- Architecture Notes
.
├── backend/ # NestJS API (TypeScript)
│ ├── src/
│ │ ├── auth/ # Registration, login, KYC, JWT
│ │ ├── stellar/ # Stellar SDK wrapper (escrow, tokens, memos)
│ │ ├── queue/ # RabbitMQ client (async jobs)
│ │ └── database/ # TypeORM config + migrations
│ └── package.json
├── frontend/ # Next.js 14 app (TypeScript + Tailwind)
│ └── src/app/
├── docker-compose.yml # PostgreSQL + RabbitMQ
└── .kiro/specs/ # Feature specs (requirements, design, tasks)
- Node.js >= 20
- npm >= 10
- Docker + Docker Compose
- A Stellar testnet account (for Stellar-related work)
git clone https://github.com/Agri-fund/agri-fi.git
cd agric-onchain
# Backend
cd backend && npm install
# Frontend
cd ../frontend && npm installdocker compose up -dThis starts PostgreSQL on port 5432 and RabbitMQ on port 5672 (management UI at http://localhost:15672, credentials: guest/guest).
cp backend/.env.example backend/.envFill in the required values — see Environment Variables below.
cd backend
npm run migration:run# Backend (port 3001)
cd backend && npm run start:dev
# Frontend (port 3000)
cd frontend && npm run devThe API will be available at http://localhost:3001 and the frontend at http://localhost:3000.
Copy backend/.env.example to backend/.env and update the values:
| Variable | Description | Required |
|---|---|---|
DATABASE_HOST |
PostgreSQL host | yes |
DATABASE_PORT |
PostgreSQL port (default: 5432) | yes |
DATABASE_USER |
DB username | yes |
DATABASE_PASSWORD |
DB password | yes |
DATABASE_NAME |
DB name (agric_onchain) |
yes |
JWT_SECRET |
Secret for signing JWTs | yes |
JWT_EXPIRES_IN |
Token expiry (e.g. 7d) |
yes |
RABBITMQ_URL |
RabbitMQ connection URL | yes |
STELLAR_NETWORK |
testnet or mainnet |
yes |
STELLAR_HORIZON_URL |
Horizon API URL | yes |
STELLAR_PLATFORM_SECRET |
Platform Stellar secret key | yes |
STELLAR_PLATFORM_PUBLIC |
Platform Stellar public key | yes |
ENCRYPTION_KEY |
AES-256 key for escrow secrets at rest | yes |
IPFS_GATEWAY |
IPFS/web3.storage API URL | optional |
IPFS_TOKEN |
web3.storage API token | optional |
AWS_REGION |
S3 region (fallback storage) | optional |
AWS_ACCESS_KEY_ID |
S3 access key | optional |
AWS_SECRET_ACCESS_KEY |
S3 secret key | optional |
AWS_S3_BUCKET |
S3 bucket name | optional |
NOTIFICATIONS_ENABLED |
Set to false to disable sending emails | optional |
SMTP_HOST |
SMTP server host for sending emails | optional |
SMTP_PORT |
SMTP server port | optional |
SMTP_USER |
SMTP authentication user | optional |
SMTP_PASS |
SMTP authentication password | optional |
EMAIL_FROM |
Sender address for emails | optional |
For Stellar work, generate a testnet keypair at https://laboratory.stellar.org and fund it via Friendbot.
Repository CI expects a funded Stellar testnet secret named STELLAR_PLATFORM_SECRET_TESTNET in GitHub Actions secrets. Use a dedicated testnet-only keypair and keep it funded with Friendbot so Stellar-dependent integration tests can create escrow accounts and submit transactions without falling back to an unfunded random account.
When the secret is not configured, CI sets STELLAR_INTEGRATION_TESTS=false and logs a notice. Any test that requires Stellar testnet access should check that flag and skip itself when the flag is false. Unit tests that mock Stellar behavior still run normally.
| Variable | Description | Required |
|---|---|---|
NEXT_PUBLIC_API_URL |
Base URL of the backend the frontend talks to (e.g. http://localhost:3001 for local dev). Baked into the client bundle at next build time. |
yes for next build |
The marketplace pages (src/app/marketplace/**) are rendered on demand (export const dynamic = 'force-dynamic') so pnpm run build does not require a reachable backend. If you add new server components that fetch from the API, either mark them force-dynamic or wrap the fetch in try/catch so the build can continue on transient failures.
Migrations live in backend/src/database/migrations/. TypeORM is configured with synchronize: false — always use migrations for schema changes.
# Run all pending migrations
npm run migration:run
# Revert the last migration
npm run migration:revert
# Generate a new migration (after editing entities)
npm run typeorm migration:generate -- -d src/database/data-source.ts src/database/migrations/YourMigrationNameMigration file naming convention: {timestamp}-{PascalCaseName}.ts
Important: New query patterns require index review to prevent performance degradation as tables grow.
- Before adding new queries: Consider if they need indexes, especially for
WHERE,JOIN, andORDER BYclauses - Composite indexes: Create for multi-column filters (e.g.,
(trade_deal_id, status)for investment availability queries) - Foreign key indexes: Ensure all foreign key columns have indexes for efficient joins
- Pessimistic locks: Queries under
pessimistic_writelocks must use indexes to avoid full table scans - Test with EXPLAIN ANALYZE: Verify queries use index scans, not sequential scans
When in doubt, add the index — PostgreSQL query planner will choose the most efficient execution path.
The backend uses Jest for unit/integration tests and fast-check for property-based tests.
# Run all tests
cd backend && npm test
# Run a specific test file
npm test src/auth/auth.service.spec.ts
# Run with coverage
npm run test:cov- Unit tests go in
*.spec.tsfiles co-located with the source file they test. - Property-based tests use
fast-checkand must run a minimum of 100 iterations. - Each property test must include a comment referencing its spec property:
// Feature: agric-onchain-finance, Property 1: token_count = floor(total_value / 100) - Do not use mocks to make tests pass — tests must validate real logic.
- All tests must pass before a PR can be merged.
- TypeScript strict mode is enabled — no implicit
any. - NestJS conventions: one module per feature, services handle business logic, controllers handle HTTP.
- DTOs use
class-validatordecorators for input validation. - Entities use TypeORM decorators; no raw SQL outside migrations.
- Keep services free of HTTP concerns (
HttpExceptionis fine, but noRequest/Responseimports in services). - Stellar interactions go through
StellarServiceonly — never call the SDK directly from other services.
The project uses structured logging with nestjs-pino for better observability and debugging:
- Use PinoLogger: Inject
PinoLoggerinstead of NestJSLoggerin all services - Set context: Always call
this.logger.setContext(ServiceName.name)in constructors - Structured data: Use objects for log data, strings for messages:
// Good this.logger.info({ userId, dealId, amount }, 'Investment created successfully'); // Bad this.logger.info(`Investment created for user ${userId} deal ${dealId} amount ${amount}`);
- Log levels:
info: Normal operations (deal created, payment processed)warn: Recoverable issues (retry attempts, validation warnings)error: Failures that require attention (Stellar errors, database failures)
- Correlation IDs: All logs automatically include correlation IDs for request tracing
- No console.log: Never use
console.login service files — always use the injected logger
Run the linter before committing:
cd backend && npm run lint- Branch from
mainfor all changes. - Branch naming:
feat/<short-description>,fix/<short-description>,chore/<short-description> - Commit messages follow Conventional Commits:
feat(auth): add KYC document submission endpoint fix(stellar): handle 404 on getTransactionStatus chore(deps): upgrade stellar-sdk to 12.3.0 - Keep commits focused — one logical change per commit.
- Make sure all tests pass:
npm test - Make sure the linter is clean:
npm run lint - Open a PR against
mainwith a clear description of what changed and why. - Reference any related spec task (e.g.
Implements task 4.1 from .kiro/specs/agric-onchain-finance/tasks.md). - PRs require at least one review before merging.
- PostgreSQL is the source of truth for application state.
- Stellar is the source of truth for payment finality — always verify on-chain before updating DB status.
- RabbitMQ handles all async Stellar jobs (asset issuance, escrow release). Never submit Stellar transactions synchronously in a request handler.
- Escrow secret keys are stored encrypted at rest using AES-256. Never log or expose them.
- All Stellar interactions target testnet during development. Switch to mainnet by setting
STELLAR_NETWORK=mainnetand updatingSTELLAR_HORIZON_URL. - The
StellarServiceis a global NestJS provider — inject it wherever blockchain operations are needed.