Put money behind your word.
Pact is a social accountability app for commitments with real stakes. A user creates a pact, names trusted validators, submits proof when the work is done, and the group votes on whether the commitment was kept.
Stakes and escrow are done with Solana.
- Create commitments with a title, deadline, stake amount, and private validator group.
- Organize commitments inside circles.
- Authenticate with Supabase email/password.
- Lock and track stake through the backend escrow layer.
- Upload proof images for validator review.
- Resolve commitments automatically when a voting majority is reached.
- Show wallet, circle, validator, and commitment state in the mobile app.
pact/
backend/ Fastify API, Supabase service access, auth verification, escrow logic
mobile/ Expo Router app for iOS, Android, and web
supabase/ Local Supabase config and database migrations
Pact uses Supabase for identity and data storage, with the backend enforcing all protected business actions.
- The mobile app signs in through Supabase Auth.
- Supabase returns a user session and access token.
- The mobile app sends that token to the Fastify backend.
- The backend verifies the token with Supabase Auth.
- The backend maps the Supabase identity to
public.users.supabase_auth_user_id. - The backend reads and writes business data with Supabase service-role access.
The client never supplies trusted userId, creatorId, or validatorId values for
protected actions. Those are derived from the authenticated token on the backend.
- A signed-in user creates a pact and chooses 2 to 3 validators.
- The backend creates or checks the user wallet and records the escrow state.
- The pact enters
awaiting_proof. - The creator uploads proof and optional notes.
- The pact enters
awaiting_votes. - Assigned validators vote
approveorreject. - The backend resolves the pact as soon as approval or rejection has a majority.
- A
wonpact releases the stake; alostpact forfeits it.
Resolved and cancelled commitments cannot receive more votes.
Install dependencies in each package:
cd backend
npm install
cd ../mobile
npm installCopy the environment examples:
cp backend/.env.example backend/.env
cp mobile/.env.example mobile/.envSet the Supabase values in both files. The backend needs the Supabase URL and service-role key. The mobile app needs the Supabase URL, anon key, and API base URL.
For local development on the same machine, the mobile API URL can be:
EXPO_PUBLIC_API_BASE_URL=http://127.0.0.1:5001
For Expo Go on a physical phone, use your computer's LAN IP instead:
EXPO_PUBLIC_API_BASE_URL=http://YOUR_COMPUTER_LAN_IP:5001
127.0.0.1 on a phone points at the phone itself, not your computer.
Start the backend:
cd backend
npm run devStart the mobile app:
cd mobile
npm run startUseful mobile targets:
npm run ios
npm run android
npm run webIf the Expo QR code does not open on a phone, make sure the phone and computer are on the same network, the Metro URL is reachable from the phone, and the mobile API base URL uses the computer's LAN IP.
The supabase/ folder contains the local configuration and migrations for users, wallets,
circles, invites, commitments, proofs, votes, and storage.
Backend scripts:
cd backend
npm run supabase:start
npm run supabase:reset
npm run supabase:stopUse supabase db reset through the package script when you want to rebuild local schema
from migrations.
Public:
GET /healthGET /invites/:code
Authenticated:
GET /auth/meGET /usersGET /wallets/mePOST /circlesGET /circlesGET /circles/:circleIdPOST /circles/:circleId/invitesPOST /invites/:code/acceptPOST /commitmentsGET /commitmentsGET /commitments/:idPOST /commitments/:id/proof-imagePOST /commitments/:id/submit-proofPOST /commitments/:id/votesPOST /commitments/:id/resolve
The mobile app should not need to call POST /commitments/:id/resolve for normal voting.
Casting a vote already triggers automatic resolution when the result is mathematically
decided.
Type-check the mobile app:
cd mobile
npm exec -- tsc --noEmitType-check the backend:
cd backend
npm exec -- tsc --noEmitRun backend tests on macOS/Linux:
cd backend
SUPABASE_URL='http://localhost:54321' \
SUPABASE_SECRET_KEY='test-secret' \
ESCROW_MODE='mocked' \
SOLANA_DEMO_TOKEN_MINT='DEMO_USDC_MOCK_MINT' \
SOLANA_RPC_URL='https://api.devnet.solana.com' \
npx tsx --test test/*.test.tsThe npm test script currently uses PowerShell syntax.
- Proof images are uploaded as base64 data URLs and stored through Supabase Storage.
- The proof upload route has a larger request body limit to account for base64 expansion.
- Escrow can run in mocked mode locally or be configured for SPL token movement.
- Expiration-based resolution should be run by a backend scheduler or maintenance job; majority vote resolution happens when a vote is cast.