Decentralized P2P Escrow · Stellar Blockchain · MVP monorepo (monorepository)
dApp-SafeTrust is the MVP assembly monorepo — it brings together curated slices from
frontend-SafeTrustandbackend-SafeTrustinto a single runnable application. One command starts everything.
🚀 Quick Start · 🏗️ Architecture · 📁 Structure · 🧩 Apps & Packages · 🛠️ Development · 🤝 Contributing
SafeTrust is a decentralized platform for secure P2P transactions. It holds funds in tamper-proof blockchain escrow contracts on the Stellar network via the TrustlessWork API — no intermediaries, no hidden fees, full on-chain transparency.
The core MVP flow:
Tenant finds property → clicks PAY → connects Freighter wallet
→ escrow deployed on Stellar → funds locked until agreement fulfilled
→ owner receives funds on release · tenant recovers deposit on dispute
This repository is the integration layer that makes that flow runnable end-to-end.
dApp-SafeTrust uses a decoupled, API-first architecture. Hasura acts as the middleware — it auto-generates a GraphQL API from the database tables, eliminating hand-written REST controllers.
┌─────────────────────────────────────────────────────────────────┐
│ Stellar Blockchain │
│ (TrustlessWork API) │
└──────────────────────────┬──────────────────────────────────────┘
│ HTTP (signed XDR)
┌──────────────────────────▼──────────────────────────────────────┐
│ apps/api (Node.js) │
│ Firebase Auth · Escrow Deploy · Webhook handlers │
└──────────────────────────┬──────────────────────────────────────┘
│ SQL
┌──────────────────────────▼──────────────────────────────────────┐
│ infra/hasura (MIDDLEWARE) │
│ Auto-generated GraphQL API · JWT validation │
│ Tables: users · escrows · bid_requests · milestones │
└──────────────────────────┬──────────────────────────────────────┘
│ GraphQL (queries · mutations · subscriptions)
┌──────────────────────────▼──────────────────────────────────────┐
│ apps/web (Next.js 14) │
│ Apollo Client · Firebase Auth · Freighter Wallet │
│ │
│ /login → /dashboard → PAY → Escrow status live │
└─────────────────────────────────────────────────────────────────┘
Key principle: apps/web never talks to apps/api directly for data. All reads go through Hasura GraphQL. The API only handles write-heavy operations (auth sync, escrow deploy) that need business logic.
dApp-SafeTrust/
├── apps/
│ ├── web/ ← Next.js 14 frontend (App Router)
│ │ ├── src/
│ │ │ ├── app/
│ │ │ │ ├── login/ ← /login page route
│ │ │ │ ├── register/ ← /register page route
│ │ │ │ └── dashboard/ ← /dashboard page route
│ │ │ ├── components/
│ │ │ │ ├── auth/ ← Login.tsx · Register.tsx · wallet modals
│ │ │ │ ├── booking/ ← BookingEscrowWrapper.tsx
│ │ │ │ ├── dashboard/ ← EscrowsByStatus · EscrowTable · EscrowDashboard
│ │ │ │ ├── ui/ ← shadcn/ui primitives (Button · Input · Card…)
│ │ │ │ └── wallet/ ← Freighter wallet connection
│ │ │ ├── core/
│ │ │ │ └── store/ ← Zustand global auth state
│ │ │ ├── graphql/
│ │ │ │ └── documents/ ← escrows.graphql · booking.graphql
│ │ │ └── lib/
│ │ │ ├── apollo-client.ts ← Apollo Client + authLink
│ │ │ └── firebase.ts ← Firebase client SDK init
│ │ ├── codegen.ts ← graphql-codegen config
│ │ └── package.json ← @safetrust/web
│ │
│ └── api/ ← Node.js + Express backend
│ ├── webhook/
│ │ ├── prepare-escrow-contract.js ← Hasura Action handler
│ │ ├── escrow-deploy.js ← submit signed XDR to TrustlessWork
│ │ ├── webhooks.js ← escrow status webhook receiver
│ │ └── handlers/
│ │ └── auth/
│ │ ├── auth-middleware.js ← Firebase token verification
│ │ ├── sync-route.js ← POST /api/auth/sync-user
│ │ └── user-sync.js ← upsert user into public.users
│ └── package.json ← @safetrust/api
│
├── infra/
│ └── hasura/ ← MVP-only Hasura configuration
│ ├── migrations/
│ │ └── safetrust/ ← safetrust tenant migrations only
│ │ ├── 1731908059919_create_uuid_extension/
│ │ ├── 1731908676359_create_users/
│ │ ├── 1731909024829_create_user_wallets/
│ │ ├── 1731909059420_create_trustless_work_escrows/
│ │ ├── 1731909059421_create_escrow_milestones/
│ │ ├── 1731909059422_create_trustless_work_webhook_events/
│ │ ├── 1732588166945_create_apartments/
│ │ ├── 1732865994413_create_bid_tables/
│ │ └── <timestamp>_create_escrows_table/
│ ├── metadata/
│ │ ├── base/ ← Hasura base config (actions, cron triggers)
│ │ └── tenants/
│ │ └── safetrust/ ← table tracking · permissions · relationships
│ └── seeds/
│ └── safetrust/
│ ├── users_seed.sql
│ ├── apartments_seed.sql
│ └── bid_requests_seed.sql
│
├── packages/
│ ├── types/ ← shared TypeScript interfaces
│ │ └── src/
│ │ ├── escrow.ts ← EscrowStatus · EscrowTransaction · Milestone
│ │ ├── user.ts ← User · UserWallet
│ │ └── index.ts
│ └── graphql/ ← codegen output (auto-generated, do not edit)
│ └── generated/
│ └── index.ts ← typed Apollo hooks consumed by apps/web
│
├── docker-compose.yml ← starts postgres + hasura + api + web
├── pnpm-workspace.yaml
├── turbo.json
└── .env.example
| Package | Description | Port |
|---|---|---|
apps/web |
Next.js 14 frontend — auth, dashboard, escrow views | 3001 |
apps/api |
Node.js backend — Firebase auth, escrow deploy, webhooks | 3000 |
infra/hasura |
Hasura GraphQL engine + PostgreSQL migrations | 8080 |
packages/types |
Shared TypeScript types consumed by both apps | — |
packages/graphql |
Generated Apollo hooks (output of codegen) | — |
| Tool | Version | Install |
|---|---|---|
| Docker + Docker Compose | latest | docs.docker.com |
| Node.js | ≥ 18 | nodejs.org |
| pnpm | ≥ 8 | npm install -g pnpm |
git clone https://github.com/safetrustcr/dApp-SafeTrust.git
cd dApp-SafeTrustcp .env.example .envOpen .env and fill in the required values:
# Firebase (get from Firebase Console → Project Settings → Service Accounts)
FIREBASE_PROJECT_ID=safetrust-890d0
FIREBASE_CLIENT_EMAIL=your-service-account@safetrust-890d0.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
# Firebase client-side (get from Firebase Console → Project Settings → General)
NEXT_PUBLIC_FIREBASE_API_KEY=your-api-key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=safetrust-890d0.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=safetrust-890d0
# TrustlessWork API (get from docs.trustlesswork.com)
TRUSTLESSWORK_API_URL=https://dev.api.trustlesswork.com
TRUSTLESSWORK_API_KEY=your-api-key
# Stellar (testnet addresses)
NEXT_PUBLIC_PLATFORM_ADDRESS=your-platform-stellar-address
NEXT_PUBLIC_USDC_ADDRESS=usdc-trustline-address-on-testnet
NEXT_PUBLIC_STELLAR_NETWORK="Test SDF Network ; September 2015"
# Hasura (keep defaults for local dev)
HASURA_GRAPHQL_ADMIN_SECRET=myadminsecretkey
NEXT_PUBLIC_HASURA_GRAPHQL_URL=http://localhost:8080/v1/graphql
NEXT_PUBLIC_HASURA_WS_URL=ws://localhost:8080/v1/graphqldocker compose up -dThis starts four services in the correct order:
postgres → hasura (applies migrations + seeds) → api → web
| Service | URL | Expected |
|---|---|---|
| Frontend | http://localhost:3001 | Redirects to /login |
| Hasura Console | http://localhost:8080/console | Tables visible |
| API health | http://localhost:3000/health | { "status": "ok" } |
Run UP checks with the PowerShell orchestrator (from the repo root):
.\infra\hasura\verification\run_checks.ps1The script discovers verify_*.sql files under infra/hasura/verification/, executes each via docker compose exec, and prints a colour-coded pass / FAIL summary. It exits with code 1 if any check fails, making it suitable for CI.
Available checks
| Script | What it verifies |
|---|---|
verify_user_wallets.sql |
UP migration — table exists with correct columns, types, defaults, PK, unique, check, and FK constraints |
verify_down_user_wallets.sql |
DOWN migration — public.user_wallets is absent after rollback |
By default it runs only UP checks (-Phase up) so it does not mix contradictory assertions.
You can also run a single check directly:
# Up migration shape check (wraps assertions in a transaction then ROLLBACK)
docker compose exec -T postgres psql -U postgres -d postgres \
-v ON_ERROR_STOP=1 -f infra/hasura/verification/verify_user_wallets.sql
# Down migration check (run only after applying the down migration)
docker compose exec -T postgres psql -U postgres -d postgres \
-v ON_ERROR_STOP=1 -f infra/hasura/verification/verify_down_user_wallets.sqlRun the DOWN phase through the orchestrator:
.\infra\hasura\verification\run_checks.ps1 -Phase downOverride defaults when your compose service name or database differ:
.\infra\hasura\verification\run_checks.ps1 -Phase up -PgService db -PgDatabase safetrustpnpm installpnpm devTurborepo starts apps/web and apps/api in parallel, respecting the dependency graph.
pnpm dev --filter @safetrust/web
pnpm dev --filter @safetrust/apiRequires Hasura to be running (docker compose up hasura -d):
pnpm codegen --filter @safetrust/webThis introspects the Hasura schema and writes typed Apollo hooks to packages/graphql/generated/index.ts.
pnpm buildTurborepo builds packages first (types → graphql), then apps.
pnpm testHasura automatically applies all migrations and seeds from infra/hasura/ on first start.
| Table | Purpose |
|---|---|
public.users |
Authenticated users (synced from Firebase) |
public.user_wallets |
Stellar wallet addresses per user |
public.apartments |
Property listings |
public.bid_requests |
Tenant interest / rental offers |
public.trustless_work_escrows |
Blockchain mirror — on-chain escrow state |
public.escrow_milestones |
Release schedule per escrow |
public.escrow_transactions |
SafeTrust business transaction log |
public.trustless_work_webhook_events |
Inbound events from TrustlessWork |
public.escrows |
Single-release security deposit escrows |
http://localhost:8080/console
Admin secret: myadminsecretkey
docker compose down -v # removes volumes
docker compose up -d # fresh start, migrations re-applied| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/health |
— | Service health check |
POST |
/api/auth/sync-user |
Bearer token | Sync Firebase user → public.users |
POST |
/prepare-escrow-contract |
Bearer token | Hasura Action: create escrow payload |
POST |
/api/escrow/deploy |
Bearer token | Submit signed XDR to TrustlessWork |
POST |
/webhooks/escrow_status_update |
— | Receive escrow status from TrustlessWork |
POST |
/webhooks/firebase/user-created |
HMAC | Firebase user creation webhook |
This repository is part of the Wave contributor program. Issues are batched into scoped sprints so contributors can work in parallel without blocking each other.
| Batch | Focus | Status |
|---|---|---|
| Batch 1 | apps/web/ structure — copy frontend slices, scaffold shell |
🟠 Active |
| Batch 2 | apps/api/ slice + infra/hasura/ MVP migrations |
🔜 Next |
| Batch 3 | Integration A→B→C — Firebase auth, GraphQL wiring, PAY button | 🔜 Planned |
Issues in Batch 1 may have unresolved imports. Replace them with:
// TODO: wire in Batch 2 — @/core/store/data
const useGlobalAuthenticationStore = () => ({ address: null });This keeps the build green while the dependent issue is in progress.
- Read the Contributing Guide
- Follow the Git Guidelines
- Include a Loom video in your PR showing before/after
- Link the issue your PR closes
| Repository | Purpose |
|---|---|
| frontend-SafeTrust | Full Next.js frontend (source for apps/web/ slices) |
| backend-SafeTrust | Full Node.js backend (source for apps/api/ slices) |
| landing-SafeTrust | Marketing landing page |
- TrustlessWork API Docs
- Hasura GraphQL Docs
- Stellar Developer Docs
- Freighter Wallet API
- Firebase Auth Docs
Built with 🔐 by the SafeTrust team · safetrustcr.vercel.app