Thanks for taking the time to contribute. AirFlex is an open-source P2P airtime and data marketplace built on Stellar — every improvement, whether it's a bug fix, new feature, or documentation update, helps the community.
Please read this guide before opening issues or pull requests.
- Code of Conduct
- Getting Help
- Project Structure
- Development Setup
- Branching Strategy
- Making Changes
- Commit Message Convention
- Pull Request Process
- Coding Standards
- Smart Contract Guidelines
- API Versioning Policy
- Reporting Bugs
- Suggesting Features
- Security Vulnerabilities
Be respectful. Harassment, discrimination, or hostile behaviour of any kind will not be tolerated. When in doubt, default to kindness.
- Open a GitHub Discussion for questions about the codebase or architecture.
- Open a GitHub Issue to report bugs or request features.
- Read the docs/ folder — it covers architecture, the API, the smart contract, and environment setup in detail.
airflex/
├── apps/
│ └── docs-site/ # Public docs site (Next.js + Nextra) — docs.airflex.io
├── contracts/ # Soroban smart contracts (Rust)
│ └── escrow/ # Escrow contract
├── docs/ # Project documentation
├── frontend/
│ └── app/ # Next.js application (TypeScript + Tailwind CSS)
├── server/
│ └── src/
│ ├── middleware/ # Express middleware (auth, etc.)
│ ├── routes/ # API route handlers
│ ├── services/ # External integrations (Stellar SDK, Paystack, etc.)
│ ├── types/ # Shared TypeScript types
│ ├── db.ts # PostgreSQL connection pool
│ └── index.ts # Server entry point
├── .gitignore
├── CONTRIBUTING.md # This file
├── LICENSE
└── README.md
| Tool | Minimum Version | Install |
|---|---|---|
| Node.js | 20.x | https://nodejs.org |
| npm | 10.x | Bundled with Node.js |
| PostgreSQL | 15.x | https://www.postgresql.org |
| Git | any | https://git-scm.com |
| Rust | 1.80+ | https://rustup.rs — required only for contract work |
| stellar-cli | 27.x | See below — required only for contract work |
Windows:
winget install -e --id Stellar.StellarCLImacOS / Linux:
cargo install --locked stellar-cligit clone https://github.com/<your-username>/Airflex.git
cd Airflex
git remote add upstream https://github.com/dark-sarge/Airflex.gitcd server
cp .env.example .env # fill in your values — see docs/environment.md
npm install
npm run dev # starts on http://localhost:3001Verify the server is healthy:
curl http://localhost:3001/health
# → {"status":"ok","timestamp":"..."}cd frontend
echo "NEXT_PUBLIC_API_URL=http://localhost:3001" > .env.local
npm install
npm run dev # starts on http://localhost:3000createdb airflex
# Run the schema from docs/getting-started.mdFull local setup instructions are in docs/getting-started.md.
| Branch | Purpose |
|---|---|
main |
Stable, production-ready code. Direct pushes are not allowed. |
feat/<short-description> |
New features |
fix/<short-description> |
Bug fixes |
docs/<short-description> |
Documentation-only changes |
refactor/<short-description> |
Code changes with no behaviour change |
chore/<short-description> |
Build, dependencies, config |
contract/<short-description> |
Soroban smart contract changes |
Always branch off main:
git checkout main
git pull upstream main
git checkout -b feat/your-feature-nameLink your branch to an issue:
Include the issue number in your branch name or commit message where applicable, e.g.
feat/marketplace-root-page-issue-1.
- Keep changes focused — one logical change per pull request.
- Read the relevant existing code before writing new code. Match existing patterns, naming conventions, and libraries rather than introducing new ones.
- Update or add documentation in
docs/if your change affects the API, architecture, environment variables, or smart contract. - Do not commit secrets,
.envfiles, private keys, or credentials. - Do not commit
.vscode/,.idea/, or other editor-specific directories — they are git-ignored for a reason.
AirFlex follows Conventional Commits.
<type>(<scope>): <short summary>
[optional body]
[optional footer — e.g. Closes #42]
| Type | When to use |
|---|---|
feat |
A new feature |
fix |
A bug fix |
docs |
Documentation only |
refactor |
Code change with no behaviour change |
chore |
Build process, dependencies, config |
contract |
Soroban smart contract changes |
test |
Adding or updating tests |
feat(frontend): implement root marketplace page closes #1
fix(server): return 404 when trade offer not found
docs: add CONTRIBUTING guide
chore(server): pin pg to 8.12.0
contract(escrow): add extend_ttl to listing storageRules:
- Use the imperative mood in the summary: "add", "fix", "implement" — not "added" or "adding".
- Keep the summary line under 72 characters.
- Reference the related issue in the footer:
Closes #<number>.
- Branch is up to date with
main(git pull upstream main --rebase) - TypeScript compiles without errors (
npm run buildinserver/) - No secrets or credentials in the diff
- Relevant docs updated if applicable
- Push your branch to your fork:
git push -u origin feat/your-feature-name
- Open a pull request against
mainon the upstream repo. - Fill in the PR template completely — summary, what changed, how to test, checklist.
- Link the related issue in the PR description using
Closes #<number>.
Follow the same convention as commit messages:
feat(frontend): implement root marketplace page
fix(server): handle missing wallet on trade creation
Keep titles under 70 characters.
- At least one maintainer approval is required before merge.
- Address review comments with new commits — do not force-push over a PR under review.
- Once approved, a maintainer will squash-merge to
main.
strict: trueis enforced — no implicitany, no unchecked nulls.- Use explicit return types on all exported functions.
- Prefer
constoverlet. Never usevar. - Use Zod for all external input validation (request bodies, query params).
- Wrap async Express handlers in the
asyncHandlerhelper so errors propagate to the global error middleware. - No
console.login committed code — useconsole.errorfor errors and prefix with a context label, e.g.[trades].
- All styling via Tailwind CSS utility classes — no inline styles, no external CSS frameworks.
- Prefer Server Components for data-fetching pages. Use Client Components only when interactivity (state, event handlers, browser APIs) is needed.
- Use
aria-labeland semantic HTML elements for accessibility. - Images must have descriptive
alttext. - Keep components small and single-purpose. Extract sub-components when a file exceeds roughly 200 lines.
- Follow the patterns in
server/src/routes/trades.tsfor new route files. - Always return consistent JSON shapes —
{ data: ... }for success,{ error: "..." }for failures,{ error: "...", details: {...} }for validation errors. - Use correct HTTP status codes:
200OK,201Created,400Bad Request,401Unauthorized,404Not Found,500Internal Server Error. - Apply the
authenticatemiddleware to all routes that require a signed-in user. - New environment variables must be documented in
docs/environment.mdand added toserver/.env.example.
- Include a migration script with any schema change.
- Add indexes for columns that appear in
WHEREorORDER BYclauses. - Never store plaintext secrets — encrypt sensitive values (e.g.
stellar_secret_key) at rest.
Contract changes carry the highest risk — a deployed contract cannot be patched in place without redeployment.
require_auth()must be called on every state-changing function.extend_ttl()must be called on every new persistent storage entry.- Test all changes on testnet before opening a PR.
- Build the contract before committing:
stellar contract build
- Update
docs/smart-contract.mdwith any new or changed functions. - Keep
contracts/readme.mdStorage Schema section in sync withDataKeychanges inescrow/src/lib.rs(tiers, TTL, and off-chain query examples). - If redeployment is required, add the new contract address to
server/.env.exampleand document it in the PR description.
All API routes are prefixed with a version identifier: /api/v1/.
This makes it possible to introduce breaking changes in a future /api/v2/
without disrupting existing clients.
| Change type | Version impact | Action |
|---|---|---|
| Breaking — removing or renaming fields, changing status codes, altering semantics | New version required | Introduce /api/v2/ prefix; deprecate and eventually sunset the old version |
| Additive — new optional fields, new endpoints, new optional query params | No new version | Ship under the same /api/v1/ prefix |
Every HTTP response from the server includes:
X-Api-Version: 1
Clients may use this header to programmatically identify the API version without inspecting the URL path.
- Announce the deprecation in a GitHub Issue / Release Notes.
- Add a
DeprecationandSunsetresponse header to the old version endpoints. - Give consumers a reasonable migration window (minimum 3 months).
- Remove the old version after the sunset date.
The canonical OpenAPI 3.0 spec lives at docs/openapi.yaml.
Update it whenever you add or modify an endpoint. The spec is the contract
between the server and all consumers (frontend, mobile, third-party integrations).
Open a GitHub Issue and include:
- What happened — a clear description of the bug.
- What you expected — what should have happened instead.
- Steps to reproduce — the exact sequence of steps to trigger the bug.
- Environment — OS, Node.js version, browser (if frontend), relevant env vars (values redacted).
- Logs / screenshots — any relevant console output or error messages.
Do not open a public issue for security vulnerabilities.
Report them privately by emailing the maintainers or using GitHub's private security advisory feature. Include a description of the issue, reproduction steps, and potential impact. A fix will be prepared and coordinated before any public disclosure.
By contributing to AirFlex you agree that your contributions will be licensed under the MIT License.