Skip to content

safetrustcr/dApp-SafeTrust

Repository files navigation

SafeTrust Logo

dApp-SafeTrust

Decentralized P2P Escrow Β· Stellar Blockchain Β· MVP monorepo

License: MIT pnpm Turborepo Next.js Hasura Stellar


dApp-SafeTrust is the MVP integration monorepo β€” it wires apps/frontend (Next.js 14) and apps/api (Node.js + Express) together through a Hasura GraphQL middleware and a PostgreSQL database. One command starts everything.


πŸš€ Quick Start Β· πŸ—οΈ Architecture Β· πŸ“ Structure Β· 🧩 Apps & Packages Β· πŸ› οΈ Development Β· ⚠️ Known State Β· 🀝 Contributing


πŸ” What is SafeTrust?

SafeTrust is a decentralized platform for secure P2P rental 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

πŸ—οΈ Architecture

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 for data reads.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Stellar Blockchain                         β”‚
β”‚                    (TrustlessWork API)                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚ HTTP (signed XDR)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              services/webhook  (Node.js + Express)              β”‚
β”‚         Firebase Auth sync Β· POST /api/auth/sync-user           β”‚
β”‚         Runs inside Docker as safetrust-webhook                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚ SQL (pg pool)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              infra/hasura  (MIDDLEWARE)                         β”‚
β”‚         Auto-generated GraphQL API Β· JWT validation             β”‚
β”‚         Tables: users Β· escrows Β· bid_requests Β· milestones     β”‚
β”‚         Runs on port 8080                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚ GraphQL (queries Β· mutations Β· subscriptions)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                apps/frontend  (Next.js 14)                      β”‚
β”‚       Apollo Client Β· Firebase Auth Β· Freighter Wallet          β”‚
β”‚       Runs on port 3001                                         β”‚
β”‚                                                                 β”‚
β”‚   /login  β†’  /register  β†’  /dashboard  β†’  PAY  β†’  escrow live  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key principle: apps/frontend never talks to services/webhook directly for data reads. All reads go through Hasura GraphQL. The webhook service only handles write-heavy operations (auth sync, escrow deploy) that need business logic.


πŸ“ Project Structure

dApp-SafeTrust/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ frontend/                    ← Next.js 14 (App Router) Β· port 3001
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ layout.tsx       ← root layout β€” wraps ClientProviders
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ login/           ← /login page
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ register/        ← /register page
β”‚   β”‚   β”‚   β”‚   └── dashboard/       ← /dashboard page
β”‚   β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ auth/            ← Login.tsx Β· Register.tsx Β· wallet modals
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ escrow/          ← EscrowPayFlow Β· ProcessStepper
β”‚   β”‚   β”‚   β”‚   └── ui/              ← shadcn/ui primitives (all import from @/lib/utils)
β”‚   β”‚   β”‚   β”œβ”€β”€ config/
β”‚   β”‚   β”‚   β”‚   └── apollo.ts        ← Apollo Client Β· authLink (Firebase JWT)
β”‚   β”‚   β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ firebase.ts      ← Firebase client SDK init
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ subscription-client.ts  ← WebSocket client for subscriptions
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ utils.ts         ← cn() helper (clsx + tailwind-merge)
β”‚   β”‚   β”‚   β”‚   └── walletconnect.ts ← WalletConnect URI stub
β”‚   β”‚   β”‚   β”œβ”€β”€ graphql/
β”‚   β”‚   β”‚   β”‚   └── queries/
β”‚   β”‚   β”‚   β”‚       └── user-queries.ts  ← GET_USER query
β”‚   β”‚   β”‚   └── providers/
β”‚   β”‚   β”‚       β”œβ”€β”€ ClientProviders.tsx       ← "use client" wrapper
β”‚   β”‚   β”‚       β”œβ”€β”€ ApolloProviderWrapper.tsx ← ApolloProvider with apolloClient
β”‚   β”‚   β”‚       └── TrustlessWorkProvider.tsx ← Trustless Work SDK context
β”‚   β”‚   β”œβ”€β”€ codegen.ts               ← graphql-codegen config
β”‚   β”‚   └── package.json             ← @safetrust/web
β”‚   β”‚
β”‚   └── api/                         ← Node.js + Express Β· port 3000
β”‚       └── src/
β”‚           β”œβ”€β”€ index.js             ← Express server entry point
β”‚           β”œβ”€β”€ lib/
β”‚           β”‚   └── trustlesswork.js ← TrustlessWork API client
β”‚           └── routes/
β”‚               └── escrow/          ← deploy.handler.js Β· deploy.route.js
β”‚
β”œβ”€β”€ services/
β”‚   └── webhook/                     ← Auth sync service (runs in Docker)
β”‚       └── src/
β”‚           β”œβ”€β”€ index.js             ← Express entry Β· port 3000 (inside container)
β”‚           β”œβ”€β”€ app.js               ← CORS Β· middleware setup
β”‚           β”œβ”€β”€ config/
β”‚           β”‚   └── firebase-admin.js ← Firebase Admin SDK init
β”‚           β”œβ”€β”€ middleware/
β”‚           β”‚   └── auth.js          ← authenticateFirebase β€” verifies Bearer JWT
β”‚           β”œβ”€β”€ routes/
β”‚           β”‚   β”œβ”€β”€ index.js         ← mounts auth routes Β· /health endpoint
β”‚           β”‚   └── auth.js          ← POST /api/auth/sync-user β†’ upsert public.users
β”‚           └── services/
β”‚               └── db.js            ← PostgreSQL pool (pg)
β”‚
β”œβ”€β”€ infra/
β”‚   └── hasura/
β”‚       β”œβ”€β”€ docker-compose.yml       ← postgres (5433) Β· graphql-engine (8080) Β· webhook
β”‚       β”œβ”€β”€ bin/
β”‚       β”‚   └── dc_prep              ← bootstrap script: up β†’ wait β†’ migrate β†’ seed
β”‚       β”œβ”€β”€ metadata/
β”‚       β”‚   β”œβ”€β”€ base/                ← shared Hasura config (actions, cron triggers…)
β”‚       β”‚   β”œβ”€β”€ build/safetrust/     ← generated output β€” do not edit manually
β”‚       β”‚   └── tenants/safetrust/   ← source table YAML (permissions Β· relationships)
β”‚       β”œβ”€β”€ migrations/safetrust/    ← timestamped SQL migrations
β”‚       └── seeds/safetrust/         ← 01_users Β· 02_apartments Β· 03_bid_requests
β”‚
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ graphql/
β”‚   β”‚   └── generated/
β”‚   β”‚       └── index.ts             ← codegen output β€” do not edit (empty until codegen runs)
β”‚   └── types/
β”‚       └── src/
β”‚           └── index.ts             ← shared TypeScript interfaces
β”‚
β”œβ”€β”€ pnpm-workspace.yaml
β”œβ”€β”€ turbo.json
└── package.json                     ← root Β· scripts: dev Β· build Β· lint

🧩 Apps & Packages

Package Name Port Status
apps/frontend @safetrust/web 3001 βœ… Running β€” /login and /register compile clean
apps/api @safetrust/api 3000 βœ… Running β€” nodemon dev server
services/webhook safetrust-webhook 3000 (container) βœ… Deployed in Docker via infra/hasura/docker-compose.yml
infra/hasura Hasura + PostgreSQL 8080 / 5433 βœ… Migrations + seeds applied via bin/dc_prep
packages/types @safetrust/types β€” βœ… Linked via workspace
packages/graphql @safetrust/graphql β€” ⚠️ Empty until pnpm codegen runs

πŸš€ Quick Start

Prerequisites

Tool Version Install
Docker + Docker Compose latest docs.docker.com
Node.js β‰₯ 18 nodejs.org
pnpm β‰₯ 8 npm install -g pnpm
Hasura CLI latest npm install -g hasura-cli

1. Clone

git clone https://github.com/safetrustcr/dApp-SafeTrust.git
cd dApp-SafeTrust

2. Install dependencies

Always run from the repo root. Never run npm install or pnpm install from inside a subdirectory β€” the workspace:* protocol in package.json files will fail.

pnpm install

3. Configure environment variables

Frontend β€” apps/frontend/.env.local

# Firebase client SDK (Firebase Console β†’ Project Settings β†’ General)
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=

# Backend webhook URL (where the frontend posts auth sync)
NEXT_PUBLIC_BACKEND_URL=http://localhost:3000

# Hasura GraphQL
NEXT_PUBLIC_HASURA_GRAPHQL_URL=http://localhost:8080/v1/graphql
NEXT_PUBLIC_HASURA_WS_URL=ws://localhost:8080/v1/graphql

Backend infrastructure β€” infra/hasura/.env

POSTGRES_PASSWORD=postgrespassword
HASURA_GRAPHQL_ADMIN_SECRET=myadminsecretkey
HASURA_GRAPHQL_JWT_SECRET='{"type":"RS256","jwk_url":"https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com","audience":"<YOUR_FIREBASE_PROJECT_ID>","issuer":"https://securetoken.google.com/<YOUR_FIREBASE_PROJECT_ID>"}'

# Firebase Admin SDK (Firebase Console β†’ Project Settings β†’ Service Accounts)
FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL=          # must be the service account email, not a personal Gmail
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"

# Webhook shared secret
HASURA_EVENT_SECRET=dev-event-secret-local
WEBHOOK_URL=http://safetrust-webhook:3000

4. Start the backend stack

cd infra/hasura
bin/dc_prep

dc_prep runs in order: starts containers β†’ waits for Hasura healthz β†’ registers the database source β†’ applies migrations β†’ reloads metadata β†’ applies seeds. Takes ~30 seconds on first run.

Verify:

# Hasura console
open http://localhost:8080/console

# Check seed data
docker exec hasura-postgres-1 psql -U postgres -d postgres \
  -c "SELECT id, email FROM public.users;"

# Check webhook is alive
curl http://localhost:3000/health

5. Start the frontend and API

In a separate terminal, from the repo root:

pnpm run dev

Turborepo starts apps/frontend and apps/api in parallel.

URL Expected
http://localhost:3001 Redirects to /login
http://localhost:3001/login Login form with wallet options
http://localhost:3001/register Register form β€” Full Name, Phone, Location, Email, Password
http://localhost:8080/console Hasura console
http://localhost:3000/health { "status": "ok" }

6. Generate GraphQL types (optional, requires Hasura running)

pnpm --filter frontend run codegen

This introspects the Hasura schema and writes typed Apollo hooks to packages/graphql/generated/index.ts. The file is empty until this runs β€” it does not block auth flow but is required for escrow queries.


πŸ”Œ API Endpoints

services/webhook (runs in Docker)

Method Endpoint Auth Description
GET /health β€” Liveness check
POST /api/auth/sync-user Bearer <Firebase JWT> Upserts user into public.users after registration

apps/api (runs locally via nodemon)

Method Endpoint Auth Description
GET /health β€” Liveness check
POST /api/escrow/deploy Bearer <Firebase JWT> Submit signed XDR to TrustlessWork

πŸ—„οΈ Database

Core tables

Table Purpose
public.users Authenticated users β€” synced from Firebase on register
public.user_wallets Stellar wallet addresses per user
public.apartments Property listings
public.bid_requests Tenant rental offers (PENDING / CANCELLED / ACTIVE)
public.trustless_work_escrows On-chain escrow mirror
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

Seed data (applied by dc_prep)

  • 2 demo users (demo-tenant-uid-001, demo-owner-uid-002)
  • 2 demo apartments linked to the demo users
  • 2 demo bid requests (PENDING + CANCELLED)

Reset database

cd infra/hasura
docker compose down -v   # removes volumes
bin/dc_prep              # fresh start β€” migrations + seeds reapplied

⚠️ Known State for Contributors

This section documents the current implementation state so contributors don't spend time debugging already-known gaps.

βœ… Fully wired and tested

Feature Details
Monorepo bootstrap pnpm install from root resolves all workspace:* deps
pnpm run dev Starts both apps/frontend (port 3001) and apps/api (port 3000) via Turborepo
Backend Docker stack bin/dc_prep boots postgres + Hasura + webhook, applies migrations and seeds
/login page Renders β€” Firebase auth not yet connected to form submit
/register page Renders β€” Full Name, Phone (+506 default), Location, Email, Password fields
Apollo Client authLink reads Firebase JWT from auth.currentUser.getIdToken() β€” wired in config/apollo.ts
services/webhook POST /api/auth/sync-user upserts into public.users with COALESCE for optional fields
Hasura metadata All tables tracked, tenant + landlord roles with row-level permissions
cn() utility Lives at @/lib/utils β€” all shadcn/ui components import from here
Provider tree layout.tsx β†’ ClientProviders β†’ ApolloProviderWrapper β†’ TrustlessWorkProvider

⚠️ Stubs (compile but not fully implemented)

File Status What's needed
src/lib/walletconnect.ts Stub β€” returns "" Full WalletConnect SignClient init with project ID
packages/graphql/generated/index.ts Empty Run pnpm --filter frontend run codegen with Hasura live
src/app/dashboard/page.tsx Shell Wire useQuery(GET_USER) from src/graphql/queries/user-queries.ts
src/components/auth/Login.tsx UI renders Connect signInWithEmailAndPassword β†’ setToken() β†’ redirect /dashboard
src/components/auth/Register.tsx UI renders createUserWithEmailAndPassword β†’ getIdToken β†’ POST /api/auth/sync-user β€” logic exists but needs .env.local to run

❌ Not yet started

Feature Notes
Freighter wallet integration useWalletDetection hook exists, actual Freighter connect not wired
Escrow deploy flow apps/api/src/routes/escrow/deploy.handler.js exists, frontend PAY button not connected
GraphQL subscriptions subscription-client.ts exists, no active subscriptions in UI
Email verification flow VerifyEmail.tsx component exists, Firebase email verification not triggered

πŸ”§ Important conventions

Never use sudo for dev commands. Running pnpm install as root causes node_modules to be owned by root, breaking subsequent runs as your normal user. If you already did this:

sudo chown -R $USER:$USER node_modules

Always run pnpm commands from the repo root, not from inside apps/frontend/ or any subdirectory. The workspace:* protocol only resolves from the workspace root.

pnpm --filter frontend dev uses the package name β€” the filter value must match the name field in package.json. For this repo that name is @safetrust/web, so use:

pnpm run dev   # starts everything (recommended)
# or target a single app:
pnpm --filter @safetrust/web dev
pnpm --filter @safetrust/api dev

The node_modules root is owned-by-root if you ran pnpm install with sudo su at any point. Fix with sudo chown -R $USER:$USER node_modules from the repo root before continuing.

Firebase FIREBASE_CLIENT_EMAIL must be the service account email (the one ending in iam.gserviceaccount.com), not a personal Gmail address. Using a personal email will cause firebase-admin to throw on startup.

FIREBASE_PRIVATE_KEY newlines β€” the private key must have literal \n in the .env file. If you copy from the Firebase console JSON, replace actual newlines with \n or wrap the value in double quotes with escaped newlines.


πŸ› οΈ Development

Run all apps

pnpm run dev

Run a single app

pnpm --filter @safetrust/web dev
pnpm --filter @safetrust/api dev

Build all

pnpm build

Turborepo builds packages/types β†’ packages/graphql β†’ then apps.

Lint

pnpm lint

🀝 Contributing

Before you start

  1. Read this README fully, especially the Known State section
  2. Check existing issues β€” most missing pieces are already tracked
  3. Run the stack locally end-to-end before opening a PR

Stub convention

If your issue has an unresolved import from a package another contributor is building in parallel, stub it:

// TODO: wire in Batch 2 β€” @/core/store/data
const useGlobalAuthenticationStore = () => ({ address: null, setToken: () => {} });

This keeps the build green while the dependency is in flight.

PR checklist

  • pnpm run dev starts without errors
  • /login and /register still compile (check browser console too)
  • No new // @ts-ignore or any without a comment explaining why
  • No console.log left in production paths
  • Include a short description of what was wired and what remains stubbed
  • Link the issue your PR closes

Branch naming

feat/<issue-number>-short-description
fix/<issue-number>-short-description

πŸ”— Related Repositories

Repository Purpose
frontend-SafeTrust Full Next.js frontend (source for apps/frontend slices)
backend-SafeTrust Full Hasura GraphQL backend (source for metadata/migrations/seeds slices)
landing-SafeTrust Marketing landing page

πŸ“š Resources


Built with πŸ” by the SafeTrust team Β· safetrustcr.vercel.app

About

Interface for a decentralized platform enabling secure and trusted P2P transactions with cryptocurrency.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors