Welcome to FarmCredit! This guide explains how to contribute to our decentralized agricultural credit platform built on the Stellar network.
New contributor? You should be able to go from git clone to a merged PR in under 30 minutes by following this guide.
- Welcome & Project Overview
- Prerequisites
- Local Development Setup
- Project Architecture
- Coding Standards
- Commit Conventions
- Pull Request Process
- Issue Workflow
- Code Review Guidelines
FarmCredit is a decentralized agricultural credit platform enabling farmers and agricultural businesses to access credit through blockchain-based mechanisms. We're building on the Stellar network, enabling fast, low-cost, and accessible financial services for agricultural communities.
- Fast Transactions — Sub-second settlement times
- Low Fees — Minimal transaction costs
- Accessibility — Open, permissionless network
- Compliance — Built with regulatory frameworks in mind
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router) | 16.1.6 |
| Language | TypeScript (strict mode) | 5.x |
| Styling | Tailwind CSS v4 + shadcn/ui | 4.x |
| Blockchain | @stellar/stellar-sdk | 11.2.2 |
| Wallet | @stellar/freighter-api | 1.7.0 |
| Design System | Stellar brand colors + atomic design pattern | Custom |
| Package Manager | pnpm | 10.28.1+ |
Our design system uses brand colors available as Tailwind classes:
| Token | Value | Tailwind Class |
|---|---|---|
| Stellar Blue | #14B6E7 |
bg-stellar-blue, text-stellar-blue |
| Stellar Purple | #3E1BDB |
bg-stellar-purple, text-stellar-purple |
| Stellar Navy | #0D0B21 |
bg-stellar-navy, text-stellar-navy |
| Stellar Cyan | #00C2FF |
bg-stellar-cyan, text-stellar-cyan |
| Stellar Green | #00B36B |
bg-stellar-green, text-stellar-green |
You'll need these installed to contribute:
-
Node.js 20+ — Download
node --version # Should be v20 or higher -
pnpm 10.28.1+ — Install globally:
npm install -g pnpm@10.28.1 pnpm --version
-
Git — Download
git --version
-
Basic Stellar Knowledge (optional for wallet features)
- Familiar with Stellar network concepts
- Have tested Stellar on testnet (future wallet features)
We recommend VS Code with these extensions:
- ES7+ React/Redux/React-Native snippets —
dsznajder.es7-react-js-snippets - Prettier - Code formatter —
esbenp.prettier-vscode - ESLint —
dbaeumer.vscode-eslint - Tailwind CSS IntelliSense —
bradlc.vscode-tailwindcss - TypeScript Vue Plugin (Volar) —
Vue.volar(if working with Vue components)
git clone https://github.com/Farm-credit/stellar-app-os.git
cd stellar-app-ospnpm installThis installs all dependencies specified in pnpm-lock.yaml.
pnpm devOpen http://localhost:3000 in your browser.
Expected output:
▲ Next.js 16.1.6
- Local: http://localhost:3000
- Environments: .env.local
✓ Ready in 2.5s
Create .env.local in the repo root:
cp .env.example .env.localFor now, most features work without environment variables. When adding features requiring external services, document them in .env.example.
# Check type safety
pnpm build
# Check code quality
pnpm lint
# Generate PWA icons (if modifying app icon)
pnpm generate-iconsAll commands should pass without errors.
Problem: pnpm: command not found
- Solution:
npm install -g pnpmand restart terminal
Problem: Node.js version is too old
- Solution: Use
nvmorfnmto install Node.js 20+nvm install 20 nvm use 20
Problem: ModuleNotFoundError after git pull
- Solution: Dependencies may have changed
pnpm install
Problem: Hot reload not working
- Solution: Clear Next.js cache and restart dev server
rm -rf .next pnpm dev
Problem: TypeScript errors in IDE but pnpm build passes
- Solution: Restart TypeScript server in VS Code
- Press
Cmd+Shift+P(macOS) /Ctrl+Shift+P(Windows/Linux) - Type
TypeScript: Restart TS Server
- Press
Components are organized by complexity, not by feature. This enables reusability across pages:
components/
├── atoms/ # Smallest, single-purpose elements
├── molecules/ # Combinations of atoms
├── organisms/ # Complex sections
├── templates/ # Page-level layouts
├── providers/ # Context providers
└── ui/ # shadcn/ui base components
Smallest building blocks — typically map 1:1 to a single UI concept.
Examples:
Button.tsx— Pressable element with variantsInput.tsx— Text input fieldBadge.tsx— Status indicatorText.tsx— Typography wrapper
File structure:
components/atoms/
├── Button.tsx
├── Input.tsx
├── Badge.tsx
└── Text.tsx
Import:
import { Button } from '@/components/atoms/Button';Combinations of atoms forming distinct UI units.
Examples:
Card.tsx— Container combining flexible layoutFormField.tsx— Label + Input + Error messageBlogCard.tsx— Image + Title + Excerpt + Link
File structure:
components/molecules/
├── Card.tsx
├── FormField.tsx
└── BlogCard.tsx
Complex sections combining atoms and molecules — usually feature-specific.
Examples:
Header.tsx— Navigation + Logo + User menuWalletConnectionStep/— Freighter integration + balance displayComparisonTable.tsx— Sortable data table
File structure:
components/organisms/
├── Header/
│ ├── Header.tsx
│ ├── Navigation.tsx
│ └── UserMenu.tsx
├── WalletConnectionStep/
│ └── WalletConnectionStep.tsx
└── ComparisonTable.tsx
Page-level structural layouts — typically one per major page type.
Examples:
DashboardLayout.tsx— Sidebar + Content areaBlogPageTemplate.tsx— Featured post + Grid + Pagination
Location: components/templates/
These are provided by shadcn/ui and should not be edited directly unless extending with Stellar variants.
Location: components/ui/
Important: Only import directly; don't re-export from atoms.
┌─────────────────────────────────────┐
│ Templates (Pages) │
│ ┌───────────────────────────────┐ │
│ │ Organisms (Features) │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Molecules (Units) │ │ │
│ │ │ ┌───────────────────┐ │ │ │
│ │ │ │ Atoms (Elements) │ │ │ │
│ │ │ └───────────────────┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
stellar-app-os/
├── app/ # Next.js App Router
│ ├── globals.css # Stellar tokens + Tailwind config
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ ├── api/ # API routes
│ │ ├── health/ # Health check endpoint
│ │ ├── wallet/ # Wallet operations
│ │ └── transaction/ # Transaction handling
│ ├── dashboard/ # Dashboard pages
│ │ └── credits/ # Credits management page
│ ├── credits/ # Credit features
│ │ ├── purchase/ # Purchase page
│ │ └── compare/ # Comparison tool page
│ └── settings/ # User settings
├── components/ # All UI components
│ ├── atoms/ # Base elements
│ ├── molecules/ # Component groups
│ ├── organisms/ # Feature sections
│ ├── templates/ # Page layouts
│ ├── providers/ # Context providers
│ └── ui/ # shadcn/ui base
├── contexts/ # React contexts
│ └── WalletContext.tsx # Wallet state management
├── hooks/ # Custom React hooks
│ └── useWallet.ts # Wallet operations hook
├── lib/ # Utility functions
│ ├── stellar/ # Stellar SDK wrappers
│ │ ├── wallet.ts # Wallet utilities
│ │ ├── signing.ts # Transaction signing
│ │ └── transaction.ts # Transaction utils
│ ├── types/ # TypeScript type definitions
│ ├── utils/ # General utilities
│ ├── api/ # API client utilities
│ ├── schemas/ # Zod validation schemas
│ └── analytics.ts # Analytics utilities
├── public/ # Static assets
│ ├── manifest.json # PWA manifest
│ ├── sw.js # Service worker
│ └── icons/ # App icons
└── scripts/ # Build and utility scripts
└── generate-icons.mjs # PWA icon generator
Rule: Always import directly from the component file. Never use barrel exports (index.ts).
Correct:
import { Button } from '@/components/atoms/Button';
import { Card, CardHeader } from '@/components/molecules/Card';
import { useWallet } from '@/hooks/useWallet';Wrong:
// ❌ Do not use barrel exports
import { Button } from '@/components/atoms';
import { useWallet } from '@/hooks';- Faster builds — Explicit imports enable better tree-shaking
- Clear dependencies — Understanding what a file imports is immediate
- Easier refactoring — Moving files doesn't break wildcard imports
This project uses TypeScript strict mode. No escape hatches.
Never use any:
// ❌ Wrong
const handleClick = (e: any) => {
console.log(e.target.value);
};
// ✅ Correct
import { ChangeEvent, FC } from 'react';
const handleClick = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};Never leave variables unused:
// ❌ Wrong
const { unused, needed } = props;
return <div>{needed}</div>;
// ✅ Correct
const { needed } = props;
return <div>{needed}</div>;If your component passes a ref to a DOM element, use forwardRef:
import { forwardRef, InputHTMLAttributes } from 'react';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
}
export const Input = forwardRef<HTMLInputElement, InputProps>(({ label, error, ...props }, ref) => (
<div>
<label>{label}</label>
<input ref={ref} {...props} />
{error && <span className="text-red-500">{error}</span>}
</div>
));
Input.displayName = 'Input';Required for debugging and React DevTools:
export const MyComponent = forwardRef<HTMLDivElement, Props>((props, ref) => (
<div ref={ref} {...props} />
));
MyComponent.displayName = 'MyComponent';Always export the props interface:
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', size = 'md', ...props }, ref) => (
<button ref={ref} className={`btn-${variant} btn-${size}`} {...props} />
)
);
Button.displayName = 'Button';| Type | Convention | Example |
|---|---|---|
| Components | PascalCase |
WalletConnectionStep, DonationForm |
| Folders | kebab-case |
wallet-connection, donation-form |
| Functions | camelCase |
handleSubmit, formatBalance |
| Variables | camelCase |
isLoading, accountBalance |
| Constants | SCREAMING_SNAKE_CASE |
MAX_AMOUNT, API_BASE_URL |
| CSS Classes | kebab-case |
bg-stellar-blue, text-sm |
| Types/Interfaces | PascalCase |
WalletBalance, TransactionDetails |
| Files | kebab-case (except components) |
use-wallet.ts, wallet-utils.ts |
Always use Stellar color tokens defined in app/globals.css:
// ✅ Correct
export const Header = () => (
<header className="bg-stellar-navy text-stellar-blue">
<h1>FarmCredit</h1>
</header>
);
// ❌ Wrong
export const Header = () => (
<header className="bg-blue-600 text-blue-400">
<h1>FarmCredit</h1>
</header>
);shadcn/ui components in components/ui/ provide base styles. Wrap them in atoms to add Stellar variants:
// components/atoms/Button.tsx
import { Button as BaseButton } from '@/components/ui/button';
import { forwardRef, ButtonHTMLAttributes } from 'react';
export const Button = forwardRef<
HTMLButtonElement,
ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: 'primary' | 'secondary';
}
>(({ variant = 'primary', ...props }, ref) => (
<BaseButton
ref={ref}
className={variant === 'primary' ? 'bg-stellar-blue' : 'bg-stellar-purple'}
{...props}
/>
));
Button.displayName = 'Button';The cn() utility merges class names while respecting Tailwind precedence:
import { cn } from '@/lib/utils';
export const Card = ({ className, ...props }: any) => (
<div className={cn('rounded-lg border bg-white p-4 shadow', className)} {...props} />
);Before committing, ensure all checks pass:
# Type checking
pnpm build
# Linting and formatting
pnpm lintFix issues automatically when possible:
pnpm lint --fixThis project enforces Conventional Commits and atomic commits. Every commit must be meaningful, buildable, and revertable.
<type>(<scope>): <short description>
[optional body describing WHY and HOW]
[optional footer with breaking changes or issue references]
| Type | When to use | Example |
|---|---|---|
| feat | New feature or component | feat(wallet): add Stellar wallet connection |
| fix | Bug fix | fix(donation): correct minimum amount validation |
| docs | Documentation only | docs(contributing): add commit guidelines |
| style | Formatting, no logic change | style(button): adjust padding with Prettier |
| refactor | Code restructuring, no behavior change | refactor(dashboard): extract tab components |
| perf | Performance improvement | perf(list): memoize comparison table rows |
| test | Adding or updating tests | test(wallet): add connection flow tests |
| build | Build system or dependency changes | build(deps): upgrade Next.js to 16.1.6 |
| ci | CI configuration changes | ci: add GitHub Actions workflow |
| chore | Maintenance tasks | chore: update .gitignore |
Scopes organize commits by feature area. Use one of:
auth— Authentication featureswallet— Wallet integration (Freighter, connections)dashboard— Dashboard pages and layoutmarketplace— Marketplace/comparison featuresadmin— Admin-only featuresdonation— Donation flowscarbon— Carbon credit featuresui— UI components (atoms, molecules, organisms)layout— Layout and page structurenav— Navigation componentsconfig— Configuration filesdeps— Dependencies and package management
# Good feature commit
feat(wallet): add wallet balance display component
# Good fix commit
fix(donation): correct decimal validation for amount input
# Good docs commit
docs(contributing): add branching strategy section
# Good refactor commit
refactor(dashboard): extract credit cards into separate component
# Good chore commit
chore(deps): update TypeScript to 5.3.3To help you follow these conventions, we provide a .gitmessage template in the root of the repository. To configure Git to use it for all commits in this repository, run:
git config commit.template .gitmessageThese rules ensure your commits are safe and reviewable:
Never mix unrelated changes:
# ❌ Bad — mixes feature, fix, and styling
feat: add dashboard with tabs, fix header bug, update colors
# ✅ Good — separate commits
feat(dashboard): create page layout
feat(dashboard): add navigation tabs
fix(header): correct active link highlighting
style(colors): update Stellar token usageEvery single commit in history must pass:
pnpm build && pnpm lintThis means:
- No temporary debugging code
- All files must type-check
- No unused imports or variables
Reverting one commit must not break unrelated features:
# ❌ Bad — commit 2 depends on commit 1
# Commit 1: feat(ui): create Button atom
# Commit 2: feat(wallet): use Button in wallet module
# If someone reverts commit 1, commit 2 breaks
# ✅ Good — clean build at each step
# Commit 1: feat(ui): create Button atom with base styles
# Commit 2: feat(wallet): create wallet module
# Commit 3: feat(wallet): integrate Button componentBuild commits in logical order:
- Foundation — utilities, types, base components
- Features — pages, sections using the foundation
- Polish — styling refinements, performance
Example good sequence:
1. feat(ui): add Button atom component
2. feat(ui): add Card molecule component
3. feat(dashboard): create dashboard layout
4. feat(dashboard): add overview tab
5. feat(dashboard): add donations tab
6. style(dashboard): align spacing and padding
Example bad sequence:
1. feat(dashboard): create full dashboard with all tabs and styling
2. refactor: extract components into separate files
# Stage specific files
git add app/page.tsx
# Or stage chunks interactively
git add -p # Choose hunks to stagegit commit -m "feat(dashboard): create dashboard page layout"Include a body explaining WHY if not obvious:
git commit -m "fix(donation): correct amount validation logic
The minimum donation amount wasn't being enforced during form
submission. Added decimal validation using Zod schema before
API call.
Fixes #42"pnpm build
pnpm lintgit add components/molecules/DashboardCard.tsx
git commit -m "feat(dashboard): add dashboard card component"-
Branch from
main:git checkout main git pull origin main git checkout -b feat/<issue-number>-<short-description>
-
Branch Naming Convention:
feat/<issue-number>-<short-description> fix/<issue-number>-<short-description> docs/<issue-number>-<short-description>Examples:
feat/42-wallet-connection-modalfix/78-donation-validation-bugdocs/107-contributor-guide
Ensure everything is ready:
# Update to latest main
git checkout main
git pull origin main
git checkout <your-branch>
git rebase main
# Verify all checks pass
pnpm build # Type checking
pnpm lint # LintingFix any issues:
pnpm lint --fix # Auto-fix formattingImportant: Every PR must include a screen recording demonstrating your implementation working.
macOS:
- Press
Cmd+Shift+5 - Select "Record Selected Portion"
- Record your feature in action
- File saves to Desktop
Windows/Linux:
- Use OBS Studio (free): https://obsproject.com
- Or built-in screen recorder
Record a workflow covering:
- Loading — Start at the relevant page
- Interaction — Use your feature as a user would
- Result — Show the expected outcome
Example: For a wallet connection feature:
1. Click "Connect Wallet" button
2. Freighter popup opens and user approves
3. Wallet address displays in header
4. Balance loads and updates
Duration: 30-60 seconds is ideal
-
Push your branch:
git push origin <your-branch>
-
Open PR on GitHub:
- Click "Compare & pull request"
- Fill out the template completely (see below)
- Attach your screen recording
-
PR Template Walkthrough:
## Summary
<!-- 1-3 sentences: What does this PR do and why? -->
Implements wallet connection modal allowing users to connect
Freighter wallet. This unblocks the wallet integration feature.
## Related Issue
Closes #42
## What Was Implemented
<!-- Detailed checklist of changes -->
- [x] WalletConnectionStep component created
- [x] Freighter API integration
- [x] Error handling for failed connections
- [x] Success toast notification
- [x] Mobile responsive design
- [x] Accessibility attributes (aria-labels)
## Implementation Details
<!-- Key technical decisions and patterns -->
- Used Freighter API (via @stellar/freighter-api)
- Implemented error boundaries for connection failures
- Stored wallet address in React Context for app-wide access
- Uses Stellar color tokens for styling consistency
## How to Test
1. Checkout this branch: `git checkout feat/42-...`
2. Install and run: `pnpm install && pnpm dev`
3. Navigate to home page
4. Click "Connect Wallet" button
5. Approve connection in Freighter popup
6. Verify wallet address appears in header
7. Test on mobile (DevTools > Toggle device toolbar)
## Screenshots / Recording
[Attach screen recording here]Every PR must have:
- ✅ Linked issue — Use
Closes #<issue-number>in description - ✅ Screen recording — Attached to PR showing feature working
- ✅ Filled PR template — All sections completed
- ✅ Passing CI — All checks green (build, lint, types)
- ✅ Atomic commits — Each commit can stand alone
PRs missing a screen recording or linked issue will not be reviewed.
After submitting:
- ⏱️ 24-48 hours — Expect initial review feedback
- 💬 Respond to all comments — Either make changes or explain decisions
- 🔄 Re-request review — After addressing feedback
- ✅ Automatic merge — Maintainer merges once approved + CI passes
Each PR includes a video showing your feature working. This is important because:
- Proof of functionality — Reviewers can see it works without running code
- Faster reviews — No setup needed to understand what changed
- Documentation — Recording becomes part of the PR history
The recording should show:
- Real browser window (not just terminal)
- Your feature being used as a real user would
- Any user interactions (clicks, form fills, etc.)
- Final result clearly visible
- Browse open issues
- Look for labels:
Stellar Wave,good-first-issue,help-wanted - Check if it's claimed: Read comments to see if someone else is working on it
- Claim it: Comment
I'll work on thisorAssigned to me
Do not start work on an issue someone else has claimed without coordinating.
Comment on the issue:
Hey! I'd like to work on this. I'll implement [brief summary].
This prevents duplicate work and shows maintainers you're engaged.
Don't wait until you're stuck. Ask questions:
- In the issue: Comment with specific questions
- On Discord/Slack: If available in the repo
- In PR: Ask during review if something wasn't clear
Clear indicators you should ask:
- ❓ Unsure about expected behavior
- 🤔 Conflicting requirements
- 🛠️ Need architectural guidance
- 🔌 Integrating a new library
Reviewers evaluate PRs based on these criteria:
- ✅ Follows project conventions (naming, import style, patterns)
- ✅ No
anytypes or unused variables - ✅ Uses
forwardRef,displayNamewhere appropriate - ✅ Proper TypeScript types exported
- ✅ Atomic design pattern followed (atoms → molecules → organisms)
- ✅ Components are reusable, not feature-specific
- ✅ No circular dependencies
- ✅ Clear separation of concerns
- ✅ Uses Stellar color tokens, not arbitrary colors
- ✅ Responsive design (mobile-first)
- ✅ Tailwind classes organized logically
- ✅ No inline styles
- ✅ Feature works as described in PR
- ✅ No console errors or warnings
- ✅ All builds pass (
pnpm build,pnpm lint) - ✅ Screen recording shows working implementation
- ✅ Commits are atomic and descriptive
- ✅ PR description explains what and why
- ✅ Code comments for complex logic
- ✅ No commented-out code left behind
Must be addressed:
Reviewer: "This component doesn't have a displayName"
Author: "Good catch! Adding displayName now." [makes change]
Can address now or in follow-up:
Reviewer: "Consider extracting this into a separate component"
Author: "I think it's small enough for now, but noted for future!"
Always respond:
Reviewer: "Why did you use Context instead of props?"
Author: "Good question. Context needed for deep nesting in
the dashboard and performance advantages. Considered props
drilling but it would go 5 levels deep."
- Read comment carefully — Understand the concern
- Respond to every comment — Even if you disagree
- Make necessary changes — Push commits
- Mark as resolved — Reply
DoneorFixed in abc123 - Re-request review — Maintainer notified to review again
| Comment | What It Means | How to Respond |
|---|---|---|
"This has an any type" |
TypeScript not strict | Replace with proper type: HTMLInputElement, ChangeEvent<HTMLInputElement>, etc. |
| "Missing displayName" | Component debugging | Add: Component.displayName = "ComponentName" |
| "Use atomic import" | Fix import style | Change import { Button } from "@/components/atoms" to import { Button } from "@/components/atoms/Button" |
| "This should be a molecule" | Architecture concern | Move component to molecules/ folder and update imports |
| "No arbitrary colors" | Styling consistency | Replace bg-blue-500 with bg-stellar-blue |
| "Screen recording missing" | PR requirement | Record yourself using the feature and upload MP4 |
Before you open a PR, verify all of these are true:
- CONTRIBUTING.md exists at repo root
- Local setup works —
git clone,pnpm install,pnpm dev(no questions needed) - All sections included — Welcome, Prerequisites, Setup, Architecture, Standards, Commits, PR Process, Issues, Review Guidelines
- Examples are copy-paste ready — Exact commands, file paths, code snippets
- Atomic design documented — Clear folder structure with use cases
- Conventional Commits documented — Allowed types and scopes with examples
- Atomic commit rules documented — Good/bad examples
- PR process explained — Including screen recording requirement
- Branch naming documented — With examples
- PR template walkthrough included — All sections explained
- Code review guidelines included — What reviewers look for, how to respond
- No grammatical errors — Professional tone throughout
- All internal links work — Links to files, sections, GitHub issues
Stuck? Here's where to ask:
- 📝 General questions: Comment in the relevant issue
- 🐛 Found a bug: Open a new issue with reproduction steps
- 💡 Feature idea: Discuss in issue before implementing
- 🤝 Contributing questions: Ask in issues or pull request comments
By contributing to FarmCredit, you agree that your contributions will be licensed under the same license as the project. See LICENSE for details.
Contributing to open-source agriculture software is meaningful work. We appreciate every pull request, issue report, and question. Together, we're building tools for a more equitable agricultural future.
Happy coding! 🚀