Thank you for contributing to the TrustLink backend! This service is the automation layer that bridges the physical world and the blockchain — watching for on-chain events, tracking shipments, triggering fund releases, and keeping buyers and vendors informed in real time.
Your contributions keep the oracle running cleanly and securely.
- Code of Conduct
- Stellar Wave Program
- Before You Start
- Development Setup
- Project Structure
- Making Changes
- Coding Standards
- Commit Convention
- Pull Request Process
- Testing
- Database Migrations
- Security Vulnerabilities
- Getting Help
This is a welcoming project. Be constructive, be patient with newcomers, and assume good intent. We're building financial infrastructure for social commerce in underserved markets — the stakes are real and the community reflects that seriousness.
Harassment, dismissiveness, or toxic behaviour will not be tolerated.
This repository participates in the Stellar Wave Program — a sprint-based contribution program funded by the Stellar Development Foundation where developers earn real XLM rewards for resolving open issues.
- Browse
Stellar Waveandgood first issueissues - Sign in at drips.network/wave with GitHub
- Apply to the issue with a brief note on your approach
- Get assigned → build → open a PR before the Wave cycle closes
- Merged PR = Points = XLM rewards
| Label | Points | Scope |
|---|---|---|
complexity: trivial |
100 pts | Swagger docs, missing validation, small bug |
complexity: medium |
150 pts | New endpoint, new service method, integration test |
complexity: high |
200 pts | New module, background worker, external API integration |
⚡ Apply before the Wave sprint is fully subscribed. Maintainers move quickly during active cycles.
Before contributing, read SPECIFICATION.md — it is the authoritative specification for this project. Every code change must conform to its requirements. PRs that violate the specification will be rejected.
Key requirements from the SRD:
- No
anytypes — strict TypeScript required - Tests required for all new code — coverage must not drop below 70%
- Security first — JWT signature verification, input sanitization, PII encryption
- CI must pass — lint, typecheck, test, build, audit all required
- Conventional commits —
type(scope): description - Repository pattern — services never call Prisma directly
- New to NestJS or Node.js? → Start with
good first issue. These have full context and don't require deep Stellar knowledge. - Backend-experienced? → Look for
complexity: mediumorcomplexity: highissues. - Have an idea? → Open a GitHub Discussion before writing code. An upfront conversation prevents your PR from being closed due to design misalignment.
- Scan the issue thread for maintainer comments — the scope may have changed since it was filed.
- Check for an existing open PR on the issue — don't duplicate effort.
- For anything that touches the Stellar transaction signing flow, the auto-release worker, or the dispute resolution logic — leave a comment describing your proposed approach and wait for a maintainer thumbs-up before starting.
| Tool | Version | Notes |
|---|---|---|
| Node.js | 20+ |
Use nvm |
| npm / pnpm | latest | |
| PostgreSQL | 15+ |
Local install or Docker |
| Docker | optional | For spinning up Postgres quickly |
# 1. Fork and clone
git clone https://github.com/YOUR_USERNAME/trustlink-backend
cd trustlink-backend
# 2. Add upstream
git remote add upstream https://github.com/your-org/trustlink-backend
# 3. Install dependencies
npm install
# 4. Set up environment
cp .env.example .env
# Edit .env — testnet values are pre-filled, you need your own DB URL
# 5. Start Postgres (Docker shortcut)
docker run --name trustlink-pg \
-e POSTGRES_USER=trustlink \
-e POSTGRES_PASSWORD=trustlink \
-e POSTGRES_DB=trustlink_dev \
-p 5432:5432 -d postgres:15
# 6. Run migrations
npx prisma migrate dev
# 7. Seed the database (optional — creates test escrow records)
npx prisma db seed
# 8. Start the dev server
npm run start:devThe API will be available at http://localhost:3001.
Swagger docs: http://localhost:3001/api/docs
git fetch upstream
git rebase upstream/maintrustlink-backend/
├── src/
│ ├── app.module.ts # Root NestJS module — registers all modules
│ │
│ ├── escrow/ # Escrow module
│ │ ├── escrow.module.ts
│ │ ├── escrow.controller.ts # HTTP routes
│ │ ├── escrow.service.ts # Business logic
│ │ ├── escrow.repository.ts # DB queries (Prisma)
│ │ └── dto/
│ │ ├── create-escrow.dto.ts
│ │ └── update-shipment.dto.ts
│ │
│ ├── dispute/ # Dispute module
│ │ ├── dispute.module.ts
│ │ ├── dispute.controller.ts
│ │ ├── dispute.service.ts
│ │ └── dto/
│ │
│ ├── auth/ # SEP-10 authentication
│ │ ├── auth.module.ts
│ │ ├── auth.controller.ts # /auth/challenge + /auth/verify
│ │ ├── auth.service.ts
│ │ └── guards/
│ │ ├── jwt.guard.ts
│ │ └── admin.guard.ts
│ │
│ ├── stellar/ # Stellar SDK layer
│ │ ├── stellar.module.ts
│ │ ├── blockchain-listener.service.ts # SSE stream from Horizon
│ │ ├── contract.service.ts # Soroban contract interactions
│ │ └── horizon.service.ts # Horizon API utilities
│ │
│ ├── notifications/ # Email + SMS
│ │ ├── notifications.module.ts
│ │ ├── notifications.service.ts
│ │ └── templates/ # Email/SMS message templates
│ │
│ ├── logistics/ # Shipping carrier integrations
│ │ ├── logistics.module.ts
│ │ ├── logistics.service.ts # Carrier-agnostic interface
│ │ └── providers/
│ │ ├── terminal-africa.provider.ts
│ │ └── gigl.provider.ts
│ │
│ ├── workers/ # Background jobs
│ │ ├── auto-release.worker.ts # Cron: checks 48h delivery window
│ │ └── tracking-poll.worker.ts # Cron: polls carrier APIs
│ │
│ └── admin/ # Admin-only module
│ ├── admin.module.ts
│ ├── admin.controller.ts
│ └── admin.service.ts
│
├── prisma/
│ ├── schema.prisma
│ ├── seed.ts
│ └── migrations/
│
└── test/
├── unit/
├── integration/
└── e2e/
Key rules:
- Business logic lives in
*.service.ts— controllers handle HTTP only (parse input, call service, return response) - Database access belongs in
*.repository.ts— services should not callprismadirectly - Stellar SDK calls belong in
src/stellar/— nostellar-sdkimports elsewhere - All background jobs go in
src/workers/— nosetIntervalor ad-hoc cron calls in services
git checkout main
git pull upstream main
git checkout -b feat/your-feature-name| Type | Pattern | Example |
|---|---|---|
| Feature | feat/description |
feat/gigl-logistics-provider |
| Bug fix | fix/description |
fix/auto-release-duplicate-sign |
| Tests | test/description |
test/dispute-service-integration |
| Docs | docs/description |
docs/add-swagger-to-dispute-dto |
| Refactor | refactor/description |
refactor/extract-escrow-repository |
| Chore | chore/description |
chore/upgrade-prisma-5.x |
- TypeScript strict mode is enabled —
anytypes will be flagged in review - Run
npm run lintbefore committing — ESLint with NestJS rules must pass clean - Run
npm run formatto apply Prettier — don't submit unformatted code - Prefer
async/awaitover raw.then()chains - All thrown errors should be NestJS
HttpExceptionsubclasses — neverthrow new Error()in a controller or service
// ✅ Controller: thin — parse, delegate, return
@Post()
async createEscrow(
@Body() dto: CreateEscrowDto,
@CurrentUser() vendor: AuthUser,
): Promise<EscrowResponseDto> {
return this.escrowService.createEscrow(dto, vendor.address);
}
// ✅ Service: logic lives here
async createEscrow(dto: CreateEscrowDto, vendorAddress: string): Promise<Escrow> {
const existing = await this.escrowRepository.findByVendorAndItem(vendorAddress, dto.itemRef);
if (existing) throw new ConflictException("Duplicate escrow for this item reference");
// ... create and return
}
// ❌ Wrong — business logic leaking into controller
@Post()
async createEscrow(@Body() dto: CreateEscrowDto) {
const existing = await this.prisma.escrow.findFirst({ where: { ... } }); // DB in controller
if (existing) throw new ConflictException(...);
}Every request body must have a DTO decorated with class-validator and @ApiProperty (Swagger):
// dto/create-escrow.dto.ts
import { IsString, IsNumber, IsPositive, MinLength } from "class-validator";
import { ApiProperty } from "@nestjs/swagger";
export class CreateEscrowDto {
@ApiProperty({ example: "Vintage Nike Jacket – Size M", description: "Item name shown to buyer" })
@IsString()
@MinLength(3)
itemName: string;
@ApiProperty({ example: 50, description: "Amount in USDC" })
@IsNumber()
@IsPositive()
amount: number;
@ApiProperty({ example: 604800, description: "Shipping window in seconds (default: 7 days)" })
@IsNumber()
@IsPositive()
shippingWindow: number;
}- All Soroban contract interactions go through
ContractServiceinsrc/stellar/ - Transaction submission must use retry logic — Stellar can temporarily reject due to sequence number contention
- The system signer key (
SYSTEM_SIGNER_SECRET) must never be logged, even partially - Always verify the transaction was included in a ledger after submission — don't trust a successful
submitresponse alone - Parse Soroban contract events by
topic+dataXDR, not by position
- Never commit
.envfiles - Never hardcode keys, secrets, or contract IDs in source files — always use
process.envvia theConfigService - The
.env.examplefile must be kept up to date — if you add a new env var, add it there with a placeholder and a comment
This repo uses Conventional Commits.
<type>(<scope>): <short imperative description>
[optional body]
[optional footer: Closes #123]
Types:
| Type | Use for |
|---|---|
feat |
New endpoint, service, module, or worker |
fix |
Bug fix |
test |
New or updated tests |
docs |
Swagger annotations, README, comments |
refactor |
Code restructuring with no behaviour change |
perf |
Performance improvement |
chore |
Dependencies, CI config, tooling changes |
security |
Vulnerability fix or hardening |
Examples:
git commit -m "feat(logistics): add Terminal Africa webhook handler"
git commit -m "fix(auto-release): prevent duplicate transaction submission on retry"
git commit -m "test(dispute): add integration test for admin resolve endpoint"
git commit -m "docs(escrow): add Swagger @ApiProperty to CreateEscrowDto"
git commit -m "security(auth): add replay protection to SEP-10 challenge verification"# Format
npm run format
# Lint — must be clean
npm run lint
# Type check
npm run type-check
# Tests
npm run test
# Build (catches compilation errors)
npm run build- What changed — Plain description of the change
- Why — Link to the issue
- Tests written — Unit and/or integration tests for new code
- Swagger updated — All new/changed endpoints have
@ApiOperation,@ApiResponse, and DTO@ApiPropertydecorators - No secrets — No API keys, contract IDs, or env values hardcoded
- Migration included — If schema changed, the Prisma migration file is committed
-
Closes #123
## Summary
<!-- What does this PR do? -->
## Motivation
<!-- Why is this change needed? Link to the issue. -->
## Changes
<!-- Bullet-point key changes -->
-
-
## Testing
<!-- What tests were added? How was this manually tested? -->
## Migration
<!-- If database schema changed, describe it here -->
## Notes for Reviewer
<!-- Anything reviewers should focus on? -->
Closes #- Active Wave cycle: 48 hours
- Outside Wave: 5 business days
- 1 approving review required
- Changes to Stellar transaction signing, auto-release logic, or dispute resolution require 2 approvals
# Unit tests
npm run test
# Watch mode (during development)
npm run test:watch
# Integration tests (requires a real DB — uses a separate test DB)
npm run test:integration
# E2E tests (requires full stack)
npm run test:e2e
# Coverage report
npm run test:covUnit tests (test/unit/) — Test a single service or function in isolation. Mock all dependencies (Prisma, Stellar SDK, external APIs) using Jest mocks.
// test/unit/escrow.service.spec.ts
describe("EscrowService", () => {
let service: EscrowService;
let mockRepo: jest.Mocked<EscrowRepository>;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
EscrowService,
{ provide: EscrowRepository, useValue: mockEscrowRepository() },
{ provide: ContractService, useValue: mockContractService() },
],
}).compile();
service = module.get(EscrowService);
mockRepo = module.get(EscrowRepository);
});
it("throws ConflictException for duplicate escrow", async () => {
mockRepo.findByVendorAndItem.mockResolvedValue(existingEscrow);
await expect(service.createEscrow(dto, vendorAddress)).rejects.toThrow(ConflictException);
});
});Integration tests (test/integration/) — Test a full module with a real test database (in-memory or Docker). Use @nestjs/testing with a real Prisma client pointing to a test DB.
E2E tests (test/e2e/) — Test full HTTP request-response cycles using supertest. The full NestJS app is bootstrapped against a test DB.
New code must include tests. PRs that drop overall coverage by more than 2% will be asked to add tests before merging.
If your change requires a schema update:
# 1. Edit prisma/schema.prisma
# 2. Generate and apply the migration
npx prisma migrate dev --name describe-your-change
# 3. Regenerate the Prisma client
npx prisma generate
# 4. Commit BOTH the migration files AND schema.prismaMigration rules:
- Never edit existing migration files — create a new one
- Migration names must be descriptive:
add-dispute-resolution-timestamp, notupdate1 - Destructive migrations (dropping columns or tables) must include a comment explaining data handling
- If you're adding a non-nullable column, it must either have a
@defaultvalue or include a migration that backfills existing rows
Do not open a public GitHub issue for security vulnerabilities.
This especially applies to:
- The system signer key handling
- The auto-release transaction submission flow
- SEP-10 authentication and JWT verification
- The admin dispute resolution endpoint
Report privately to:
📧 security@trustlink.xyz (or the address in SECURITY.md)
Include:
- Description of the vulnerability
- Steps to reproduce or a proof-of-concept
- Your severity assessment
We aim to acknowledge within 48 hours and patch critical issues within 7 days.
- 💬 GitHub Discussions → Ask a question
- 🐛 GitHub Issues → Confirmed bugs only — include steps to reproduce and error logs
- 🌊 Stellar Developers Discord → discord.gg/stellardev for real-time help
Helpful resources:
- NestJS Documentation
- Prisma Docs
- Stellar Horizon API Reference
- Soroban RPC Reference
- Stellar SEP-10 Specification
- Terminal Africa API Docs
The backend is the quiet engine that makes the promise of trustless commerce actually work. Thank you for helping keep it reliable.