HackIllinois 2026 — Blockchain-backed escrow for freelance jobs & bounties.
PackedPay lets two parties lock SOL into an on-chain escrow, collaborate on a task, and release funds only when both agree the work is done. Every escrow is a real Solana PDA — no middleman, no trust required.
Creator Worker
│ │
├─ 1. Create room (set reward + stakes) │
│ └─ Auto-stakes collateral + reward │
│ │
│ 2. Worker browses open rooms ◄──────────────┤
│ │
│ 3. Worker marks interest ◄──────────────────┤
│ │
├─ 4. Creator accepts a worker │
│ │
│ 5. Worker stakes collateral ◄───────────────┤
│ └─ Room is now ACTIVE (both staked) │
│ │
│ ── work happens off-chain ── │
│ │
├─ 6a. Creator approves resolution (on-chain) │
│ 6b. Worker approves resolution (on-chain) ◄─┤
│ │
├─ 7. Creator signs resolve tx │
│ └─ Creator gets stake back │
│ └─ Worker gets stake + reward │
└───────────────────────────────────────────────┘
Key economics:
- Creator locks
creator_stake + rewardinto escrow - Worker locks
joiner_stakeinto escrow - On resolve: creator gets their stake back, worker gets their stake + the reward
- On slash: all funds go to a penalty wallet (nuclear option)
- On cancel: creator gets funds back (only before worker stakes)
┌─────────────────────┐ ┌────────────────────────┐
│ PackedPay Demo │ HTTP │ Blink API │
│ (Next.js :3000) │───────▶│ (Next.js :3001) │
│ src/ │ │ api/ │
└─────────────────────┘ └──────────┬─────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌──────────┐ ┌──────────────┐
│ Solana │ │ Supabase │
│ Devnet │ │ (Postgres) │
└──────────┘ └──────────────┘
| Component | Location | Port | Description |
|---|---|---|---|
| Blink API | api/ |
3001 | Standalone REST API — builds Solana transactions, manages rooms |
| PackedPay | src/ |
3000 | Demo frontend that consumes the API |
| StakeGuard | Solana devnet | — | Program ID: 4ixiwwbedA1p3s79zgPmqf9C2JKLJ1WkEDVtCw9yQSxf |
# 1. Install dependencies
npm install && cd api && npm install && cd ..
# 2. Set up env vars
cp .env.example .env
cp api/.env.example api/.env.local
# Fill in your Supabase + Solana RPC credentials
# 3. Apply database schema
# Run supabase/schema.sql against your Supabase project (SQL Editor in dashboard)
# 4. Run both apps
npm run dev:all
# Or individually:
# npm run dev → PackedPay on :3000
# npm run dev:api → Blink API on :3001| Variable | Where | Description |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Both | Supabase project URL |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY |
Both | Supabase anon key |
NEXT_PUBLIC_SOLANA_RPC_URL |
Both | Solana RPC (e.g. https://api.devnet.solana.com) |
NEXT_PUBLIC_API_URL |
Frontend | Blink API URL (e.g. http://localhost:3001) |
pending → open → awaiting_joiner_stake → active → resolved
│
├───→ slashed
└───→ cancelled
| Status | Meaning |
|---|---|
pending |
Room created, creator staking in progress |
open |
Creator staked, waiting for workers to show interest |
awaiting_joiner_stake |
Creator accepted a worker, waiting for their stake |
active |
Both parties staked — work is in progress |
resolved |
Both approved + on-chain resolve tx confirmed — funds released |
slashed |
Funds sent to penalty wallet |
cancelled |
Creator cancelled before worker staked |
The Solana program manages the escrow lifecycle with these instructions:
| Instruction | Who Signs | What It Does |
|---|---|---|
initializeRoom |
Creator | Creates the escrow PDA, sets stake amounts |
stake |
Creator or Worker | Deposits SOL into the escrow PDA |
approveResolve |
Creator or Worker | Records on-chain approval to resolve |
resolve |
Creator | Releases funds — creator gets stake, worker gets stake + reward |
slash |
Either party | Sends all funds to penalty wallet |
cancelRoom |
Creator | Returns funds (only before fully funded) |
The on-chain RoomEscrow account tracks:
- Stake amounts and funded status for both parties
creatorApprovedResolve/joinerApprovedResolveflags- Active/funded state
Both parties must call approveResolve on-chain before resolve can execute. This is enforced by the program — not just the database.
Base URL: http://localhost:3001/api/v1
| Method | Path | Description |
|---|---|---|
GET |
/rooms |
Browse public rooms (pagination, search, filters) |
POST |
/rooms |
Create a room — returns room + lockbox (unsigned init tx) |
GET |
/rooms/:id |
Get room details |
POST |
/rooms/:id/interest |
Mark interest in a room (as a potential worker) |
POST |
/rooms/:id/accept |
Accept a worker (creator only) |
POST |
/rooms/:id/stake |
Stake into escrow — returns unsigned stake tx |
POST |
/rooms/:id/resolve-approve |
Approve resolution — returns unsigned approveResolve tx |
POST |
/rooms/:id/resolve |
Finalize resolution — returns unsigned resolve tx (creator only) |
POST |
/rooms/:id/slash |
Slash escrow — returns unsigned slash tx |
POST |
/rooms/:id/cancel |
Cancel room — returns unsigned cancel tx |
| Method | Path | Description |
|---|---|---|
POST |
/tx/submit |
Submit a signed Solana transaction + trigger DB updates |
Every action that touches Solana follows the same pattern:
1. Frontend calls API endpoint (e.g. POST /rooms/:id/stake)
2. API builds an unsigned Solana transaction
3. API returns it as base64 in { lockbox: { action: { payload: "base64tx" } } }
4. Frontend deserializes the tx, user signs with Phantom/Solflare
5. Frontend sends signed tx to POST /tx/submit
6. API submits to Solana, waits for confirmation, updates DB
Only 2 tables — intentionally minimal:
Core escrow container with financial columns as real SQL types:
reward_amount,creator_stake_amount,joiner_stake_amount— SOL amountscreator_wallet,joiner_wallet— Solana addressescreator_funded,joiner_funded— staking statusstatus— lifecycle statemetadata(JSONB) — title, description, tags, terms, escrow PDA, tx signatures, etc.
System event log used for:
- Tracking resolve approvals (
action: 'resolve_approved'/'both_approved') - Recording stake/resolve notifications
- Audit trail of all room events
Resolution approval flags (creator_resolve_approved, joiner_resolve_approved) are derived from system messages at query time by getRoomFull() — not stored as room columns.
| Page | Route | Description |
|---|---|---|
| Home | / |
Landing page |
| Browse | /browse |
Public job board — search, filter, mark interest |
| Create | /create |
Create a new escrow room (sets reward + stakes) |
| Room | /room/:id |
Full workflow — stake, approve, resolve, chat |
| Dashboard | /dashboard |
Your rooms (created + joined) |
| Join | /join |
Enter a 6-character join code |
The room page shows a step-by-step progress indicator. Each role sees different steps:
Creator sees:
- ✅ Staked (auto-staked on creation)
- Wait for workers → Accept one
- Wait for worker to stake
- Approve Resolution (signs on-chain
approveResolvetx) - Finalize Resolution (signs on-chain
resolvetx — sends reward to worker)
Worker sees:
- Stake collateral (signs on-chain
staketx) - Approve Resolution (signs on-chain
approveResolvetx) - Wait for creator to finalize
hackillinois-2026/
├── api/ # Blink API (standalone backend)
│ └── src/
│ ├── app/api/v1/
│ │ ├── rooms/ # Room CRUD + browse
│ │ │ └── [roomId]/
│ │ │ ├── accept/ # Accept a worker
│ │ │ ├── cancel/ # Cancel room
│ │ │ ├── interest/ # Mark interest
│ │ │ ├── resolve/ # Build resolve tx
│ │ │ ├── resolve-approve/ # Build approveResolve tx
│ │ │ ├── slash/ # Build slash tx
│ │ │ └── stake/ # Build stake tx
│ │ └── tx/submit/ # Submit signed transactions
│ └── lib/
│ ├── database.ts # Supabase queries + getRoomFull
│ ├── providers/ # Direct + Custodial engine abstraction
│ │ ├── direct.ts # Builds unsigned txs for user signing
│ │ ├── custodial.ts # Platform wallet signs on behalf
│ │ └── types.ts # EscrowEngine interface
│ └── solana/
│ ├── idl.ts # StakeGuard Anchor IDL
│ └── transactions.ts # All Solana tx builders
├── src/ # PackedPay Frontend
│ ├── app/
│ │ ├── browse/ # Job board
│ │ ├── create/ # Room creation
│ │ ├── dashboard/ # My rooms
│ │ ├── room/[roomId]/ # Full escrow workflow page
│ │ └── join/ # Join by code
│ └── lib/
│ └── api.ts # API client (all fetch calls)
├── contracts/
│ └── stakeguard.rs # Anchor program source
├── supabase/
│ └── schema.sql # Database schema (2 tables)
└── package.json
- Frontend: Next.js 14, React 18, Tailwind CSS
- Backend: Next.js API routes (standalone), Supabase (Postgres)
- Blockchain: Solana devnet, Anchor framework
- Wallet: Solana Wallet Adapter (Phantom, Solflare, etc.)
All endpoints return:
{
"success": false,
"error": "Description of what went wrong"
}| Status | Meaning |
|---|---|
| 400 | Bad request / validation error |
| 403 | Not authorized (wrong wallet) |
| 404 | Room not found |
| 500 | Internal server error |