SafeTrust is a decentralized P2P escrow platform for rental transactions. Funds are held in tamper-proof smart contracts on the Stellar network via the TrustlessWork API — no intermediaries, full on-chain transparency.
- Rental deposits — A tenant pays a security deposit. Funds are locked on-chain until the rental period ends; released to the owner on fulfillment or returned on dispute resolution.
- Service agreements — A client and provider agree on milestones. Funds release per milestone as each is approved, with a neutral dispute resolver as backstop.
- P2P property rentals — Owner lists a property; tenant submits a bid; on acceptance, escrow is deployed automatically via Freighter wallet signing.
Tenant finds property → clicks PAY → connects Freighter wallet
→ SafeTrust calls POST /deployer/single-release (TrustlessWork API)
→ API returns unsigned XDR → Freighter signs it
→ POST /helper/send-transaction broadcasts to Stellar
→ funds locked on-chain until release conditions are met
┌─────────────────────────────────────────┐
│ Stellar Blockchain │
│ (TrustlessWork API) │
└───────────────┬─────────────────────────┘
│ signed XDR
┌───────────────▼─────────────────────────┐
│ services/webhook (Node + Express) │
│ Firebase Auth sync · escrow deploy │
│ Container: safetrust-webhook │
└───────────────┬─────────────────────────┘
│ SQL
┌───────────────▼─────────────────────────┐
│ infra/hasura (Hasura GraphQL) │
│ Auto-generated API · JWT auth │
│ Port 8080 │
└───────────────┬─────────────────────────┘
│ GraphQL
┌───────────────▼─────────────────────────┐
│ apps/frontend (Next.js 14) │
│ Apollo Client · Firebase · Freighter │
│ Port 3001 │
└─────────────────────────────────────────┘
| Tool | Version |
|---|---|
| Docker + Docker Compose | latest |
| Node.js | ≥ 18 |
| pnpm | ≥ 8 |
| Hasura CLI | latest |
npm install -g pnpm hasura-cligit clone https://github.com/safetrustcr/dApp-SafeTrust.git
cd dApp-SafeTrust
pnpm install # always run from repo root
⚠️ Never runpnpm installfrom inside a subdirectory —workspace:*deps only resolve from the root.
SafeTrust uses Firebase for user authentication. You need a Firebase project before running the app.
- Go to console.firebase.google.com and create a new project.
- Under Authentication → Sign-in method, enable Email/Password.
- Under Project Settings → General → Your apps, register a Web app and copy the config values.
- Under Project Settings → Service Accounts, click Generate new private key and download the JSON file. You'll need the
project_id,client_email, andprivate_keyfields from it.
# From Firebase Console → Project Settings → General → Your apps
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=
# Hasura GraphQL endpoint
NEXT_PUBLIC_HASURA_GRAPHQL_URL=http://localhost:8080/v1/graphql
NEXT_PUBLIC_HASURA_WS_URL=ws://localhost:8080/v1/graphql
# Backend webhook (auth sync)
NEXT_PUBLIC_BACKEND_URL=http://localhost:3000# PostgreSQL
POSTGRES_PASSWORD=postgrespassword
# Hasura admin secret (choose any string)
HASURA_GRAPHQL_ADMIN_SECRET=myadminsecretkey
# Firebase JWT verification — replace YOUR_FIREBASE_PROJECT_ID with your actual project ID
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 — from the service account JSON you downloaded
FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL= # ends in iam.gserviceaccount.com — not a personal Gmail
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
# Webhook
HASURA_EVENT_SECRET=dev-event-secret-local
WEBHOOK_URL=http://safetrust-webhook:3000
⚠️ FIREBASE_PRIVATE_KEYmust use literal\nfor newlines. Wrap the value in double quotes exactly as shown above.
cd infra/hasura
bin/dc_prepbin/dc_prep runs in order: starts containers → waits for Hasura health → applies migrations → reloads metadata → applies seeds. Takes ~30 s on first run.
Verify it's working:
open http://localhost:8080/console # Hasura console
curl http://localhost:3000/health # webhook: { "status": "ok" }cd infra/hasura
docker compose down -v # removes volumes
bin/dc_prep # fresh startFrom the repo root, in a separate terminal:
pnpm run devTurborepo starts apps/frontend (port 3001) and apps/api (port 3000) in parallel.
| URL | What you see |
|---|---|
http://localhost:3001 |
Redirects to /login |
http://localhost:3001/login |
Login form with wallet options |
http://localhost:3001/register |
Register form |
http://localhost:8080/console |
Hasura console |
Requires Hasura to be running:
pnpm --filter @safetrust/web run codegen- Writes typed Apollo hooks to
packages/graphql/generated/index.ts. Not required for auth flow, but needed for escrow queries.
⚠️ Not an implementation step — this section describes the TrustlessWork API for reference only.
SafeTrust deploys and funds escrow contracts via the TrustlessWork API. All calls require an x-api-key header and return an unsigned XDR transaction that must be signed by Freighter Wallet before being broadcast to Stellar.
POST /deployer/single-release → returns unsignedTransaction (XDR)
Wallet signs the XDR
POST /helper/send-transaction → broadcasts to Stellar (escrow deployed)
POST /escrow/single-release/fund-escrow → returns unsignedTransaction (XDR)
Wallet signs the XDR
POST /helper/send-transaction → broadcasts to Stellar (escrow funded)
Full API reference: docs.trustlesswork.com
Before opening a PR:
- Run
pnpm run dev— both apps must start without errors. /loginand/registermust compile clean.- No
console.login production paths, no unexplainedanyor@ts-ignore. - Link the issue your PR closes.
Branch naming: feat/<issue-number>-short-description · fix/<issue-number>-short-description
Stub convention — if your issue depends on a package another contributor is building, stub it rather than blocking:
// TODO: wire in Batch 2 — @/core/store/data
const useGlobalAuthenticationStore = () => ({ address: null, setToken: () => {} });| Repository | Purpose |
|---|---|
| frontend-SafeTrust | Full Next.js frontend (source for apps/frontend slices) |
| backend-SafeTrust | Full Hasura backend (source for migrations and seeds) |
| landing-SafeTrust | Marketing landing page |
Built with 🔐 by the SafeTrust team · safetrustcr.vercel.app
