| Feature | Status | Branch |
|---|---|---|
| IPFS upload proxy (Pinata) | ✅ Done | main |
| Stellar Horizon event streaming | ✅ Done | main |
| Sample metadata indexing | 🔄 In Progress | feat/metadata-index |
| PostgreSQL for persistence | 📋 Planned | — |
| WebSocket real-time sale feed | 📋 Planned | — |
| Royalty split config + payout ledger | 🔄 In Progress (schema + config API done, reconciliation blocked on the event indexer, #20) | feat/royalty-split-and-payout-ledger |
The Crate backend handles everything the Soroban smart contract doesn't — file storage, discovery, and analytics. The contract handles payments and licensing. The backend handles search, IPFS, and data aggregation.
The backend is intentionally lightweight. Crate's trust model is the contract. The backend is a performance layer on top of it.
- IPFS proxy — routes audio uploads to Pinata so the frontend never exposes the API key
- Analytics — aggregates producer earnings, platform volume, and trending beats by indexing Horizon events
- Sample metadata — caches off-chain data (title, genre, BPM) for fast marketplace search
- Preview clips — serves 30-second preview cuts from the IPFS gateway
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/samples |
List samples with filters (?genre=trap&bpm=140) |
GET |
/api/samples/:id |
Get single sample metadata |
POST |
/api/samples/metadata |
Save off-chain metadata after on-chain upload |
POST |
/api/samples/:id/royalty-splits |
Configure a new royalty split version (uploader-only, auth required) |
GET |
/api/samples/:id/royalty-splits |
List every split version ever configured for a sample |
GET |
/api/samples/:id/royalty-splits/current |
Get the split version currently in effect |
GET |
/api/samples/:id/payouts |
List the payout ledger for a sample |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/ipfs/upload |
Upload audio file → returns { cid, gatewayUrl } |
GET |
/api/ipfs/:cid/preview |
Serve 30-second preview clip |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/analytics/stats |
Platform totals: samples, volume, producers |
GET |
/api/analytics/earnings/:address |
Producer transaction history |
GET |
/api/analytics/trending |
Top samples by purchase count |
┌─────────────────────────────────────────────────┐
│ Express API │
│ CORS · Helmet · Rate limiting │
└──────────┬─────────────────────┬────────────────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ /samples │ │ /ipfs │
│ /analytics │ │ /preview │
└──────┬──────┘ └──────┬──────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Horizon │ │ Pinata │
│ (Stellar) │ │ (IPFS) │
└─────────────┘ └─────────────┘
| Layer | Technology |
|---|---|
| Runtime | Node.js 20, TypeScript 5 |
| Framework | Express 4 |
| Stellar | @stellar/stellar-sdk — Horizon queries + events |
| Storage | IPFS via Pinata API |
| Upload | Multer — multipart/form-data |
| Container | Docker multi-stage build |
- Node.js 20+
- A Pinata account (free tier works)
# Clone
git clone https://github.com/Crate-Protocol/crate-backend.git
cd crate-backend
# Install
npm install
# Configure
cp .env.example .env
# Start
npm run devAPI runs at http://localhost:3001
PORT=3001
# Stellar
STELLAR_NETWORK=TESTNET
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
CONTRACT_ID=CA7DGEWWS3VH5J2I4I7FFEB5UHK2MJSYWDKDQKXQM7GDNLI2IRATDTLG
# IPFS
PINATA_JWT=your_pinata_jwt
PINATA_GATEWAY=https://gateway.pinata.cloud
# CORS
ALLOWED_ORIGINS=http://localhost:5173,https://crate.fmdocker build -t crate-backend .
docker run -p 3001:3001 --env-file .env crate-backendA separate long-running worker that polls Soroban getEvents for the
contract's uploaded/licensed events and persists them to Postgres —
that's what total_sales and GET /api/analytics/stats are actually backed
by, not a live contract call or client-submitted values. Run it as its own
process, alongside the API rather than inside it:
npm run db:migrate # applies db/migrations/, including the indexer's tables
npm run dev:indexer # or: npm run build && npm run start:indexerSee .env.example for tuning (poll interval, backfill depth, page size).
src/
├── index.ts # Express app — middleware + routes
├── indexer/ # Standalone Soroban event indexer worker
│ ├── index.ts # Entrypoint (npm run dev:indexer / start:indexer)
│ ├── worker.ts # Backfill + poll loop
│ ├── sorobanEvents.ts # getEvents/getLatestLedger RPC calls
│ └── eventDecoder.ts # Raw event → DecodedEvent
├── routes/
│ ├── samples.ts # Sample CRUD + metadata
│ ├── ipfs.ts # Pinata upload proxy
│ └── analytics.ts # Stats + earnings + trending
├── services/
│ ├── stellar.ts # Horizon queries, account balance, event SSE
│ └── ipfs.ts # Pinata file upload service
├── db/
│ ├── sampleRepository.ts # samples table
│ └── indexerRepository.ts # contract_events / indexer_cursor / platform_stats
└── middleware/
└── cors.ts # CORS config
# Fork → clone → branch
git checkout -b feat/your-feature
# Make changes, then open a PR| Repo | Description |
|---|---|
| crate-frontend | React 18 + TypeScript web app |
| crate-contracts | Soroban smart contracts (Rust) |
| crate-mobile | React Native mobile app |
MIT — see LICENSE