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
