Architecture (MVP)
This MVP lets a Tutor go online with a per-minute/per-second rate, and a Student:
- match via roulette or booking
- pay only for time in call
- get auto call cutoff when balance ends or either side disconnects
- receive POAP/credential (optional)
- rate the tutor and track progress
We minimize contracts to what’s essential for time-billed calls and auto-stop.
High-level Components
graph TD
subgraph "Client"
S["Student dApp - NextJS"]:::box
T["Tutor dApp - NextJS"]:::box
W["WalletConnect"]:::box
end
subgraph "Realtime"
H["Huddle01 WebRTC Rooms"]:::svc
end
subgraph "Off-chain Services"
MM["Matchmaker API - Roulette & Booking"]:::svc
HB["Heartbeat Service - 5s signed pings"]:::svc
RL["Relayer Bot - authorized"]:::svc
NF["Notifications & Calendar"]:::svc
end
subgraph "On-chain / Protocol"
CTRL["Payment Controller - Flow Operator"]:::chain
STR["Streaming Protocol - Superfluid or Sablier"]:::chain
CRED["Credentials - POAP or SBT"]:::chain
RAT["Ratings (IPFS + on-chain hash)"]:::chain
DAO["DAO Multisig + Snapshot"]:::chain
end
S -->|auth| W
T -->|auth| W
S --> MM
T --> MM
MM --> H
S -->|join| H
T -->|join| H
S --> HB
T --> HB
H --> HB
HB --> RL
RL --> CTRL
CTRL -->|create/update/delete flow| STR
S -->|grant operator + top-ups| CTRL
STR -->|real-time stream| T
S --> CRED
T --> CRED
S --> RAT
T --> RAT
DAO --> CRED
classDef box fill:#0b5fff0d,stroke:#0b5fff,stroke-width:1px,color:#0b2540;
classDef svc fill:#ffd50014,stroke:#d4a000,stroke-width:1px,color:#5a4500;
classDef chain fill:#00a36b14,stroke:#008a59,stroke-width:1px,color:#0b3b2c;
Reading notes
- Matchmaker handles roulette/booking, then hands off to Huddle01 for the live session.
- Heartbeat watches WebRTC + pings; Relayer → Controller deletes the stream on drop.
- Controller is pre-authorized by the student (flow operator) to manage their stream non-interactively.
- Top-ups go from Student → Controller/Protocol; funds stream to Tutor in real time.
- Credentials (POAP/SBT) and Ratings form the reputation layer; DAO issues SBTs via Snapshot + Multisig.
Component 1: Session Escrow (time-billed calls)
Goal: pay the tutor only for time in call; auto-stop on cap/disconnect.
stateDiagram-v2
[*] --> Idle
Idle --> Funded: deposit(cap, rate)<br>(by Student)
Funded --> Active: startSession()<br>(both joined)
Active --> Stopping: cap reached<br>(or Student ends)
Active --> Stopping: disconnect detected<br>(heartbeat/webRTC)
Active --> Stopping: tutor ends
Stopping --> Settled: settle owed = min(elapsed, cap) * rate
Settled --> Refunded: refund remainder (cap - elapsed)*rate
Refunded --> Closed
What this component does (and only this)
- deposit(cap, rate): Student pre-funds max spend → moves to Funded.
- startSession(): Called once both are in the room → moves to Active.
- elapsed accounting: measured inside contract (block timestamps) or a “meter” submodule.
- stop triggers:
- cap reached,
- disconnect (from heartbeat/webRTC signal relayed on-chain),
- either party ends.
- settle(): pay tutor min(elapsed, cap) × rate, refund remainder to student.
- no overpaying, no post-drop leakage by construction.
Caveats
What if the user wishes to keep the current session running longer than his deposit allows? Could be a UX bug.
Component 2: Heartbeat & Auto-Stop
Purpose: guarantee that if either peer drops from a Huddle01 call, billing halts instantly.
Concept
Each participant’s client emits a signed “I’m still here” ping every N seconds to a lightweight service.
If either side fails to send two consecutive heartbeats (≈ 10 s of silence), the service calls the on-chain stopSession()
via a relayer.
sequenceDiagram
autonumber
participant Student as Student Client
participant Tutor as Tutor Client
participant Huddle as Huddle01 (WebRTC)
participant Heartbeat as Heartbeat Service
participant Relayer as Relayer Bot
participant Controller as Payment Controller
participant Stream as Streaming Protocol
%% Start of call (elsewhere: Matchmaker + Controller.startSession)
Student->>Huddle: join()
Tutor->>Huddle: join()
%% Periodic liveness pings
loop every 5s
Student->>Heartbeat: POST /ping {sessionId, sig}
Tutor->>Heartbeat: POST /ping {sessionId, sig}
end
Note over Heartbeat: tracks lastPing for both peers
%% Disconnect from Huddle or missing pings
Huddle-->>Heartbeat: peer-left / connectionState=failed
Heartbeat->>Heartbeat: OR detect missing ping > 10s
alt disconnect or missing ping
Heartbeat-->>Relayer: emit "disconnect(sessionId)"
Relayer->>Controller: stopSession(sessionId)
Controller->>Stream: deleteFlow(student -> tutor)
Note over Controller,Stream: Stream deleted → payment stops instantly
end
Key changes
- Heartbeat no longer calls escrow; it instructs the Controller to delete the live stream (Superfluid/Sablier) so billing halts immediately.
- The note is placed inside a valid branch to avoid Mermaid parse errors.
Behavior Summary
Event | Trigger | Action |
---|---|---|
✅ Normal pings | both clients responding | nothing changes |
⚠️ One peer silent > 10 s | network loss/closed tab | Heartbeat → Relayer → stopSession() |
Stop Acknowledgement | Escrow settles | tutor payout + student refund issued |
Implementation notes
- Ping = small JSON {sessionId, walletAddr, signature}.
- Verification = HMAC / signature ensures no spoofing.
- Relayer = can be Biconomy, Gelato, OZ Defender or simple script with funded key.
- Timeout tunable (5 s, 10 s, 15 s).
- Logs feed optional analytics (“average disconnect time”).
Component 3A — Matchmaker (Roulette “find tutor now”)
Purpose: pair a student with an available, matching tutor quickly, confirm the rate/cap, and hand off to escrow → call.
Assumptions
- Student specifies filters: language, level, max rate, credential (optional SBT).
- Tutor sets availability + per-second/minute rate and optional credentials.
- We pre-check student balance/cap before proposing.
sequenceDiagram
autonumber
participant Student as Student dApp
participant MM as Matchmaker API
participant Tutor as Tutor dApp
participant Wallet as Wallet/Balance
participant Escrow as Escrow Contract
participant Huddle as Huddle01
Student->>Wallet: read balance / max spend cap
Student->>MM: findNow({lang, level, maxRate, credReq, cap})
MM->>MM: filter tutors: online ∧ rate<=maxRate ∧ cred ok
alt no tutors
MM-->>Student: none available → suggest booking/waitlist
Note over Student,MM: END (no match)
else candidates exist
MM->>Tutor: proposal({rate, estCap, filters}) (30s ttl)
alt tutor accepts
Tutor-->>MM: accept
MM->>Huddle: create room token (ephemeral)
MM-->>Student: matchFound({tutor, rate, roomToken, cap})
Student->>Escrow: deposit(cap), startSessionIntent(rate,tutor)
alt deposit OK
Escrow-->>Student: sessionId
Student->>Huddle: join(roomToken)
Tutor->>Huddle: join(roomToken)
Note over Student,Tutor: handoff to Heartbeat + Escrow component
else deposit fails / too low
Escrow-->>Student: insufficient funds
MM-->>Student: retry / reduce cap
end
else tutor times out / declines
Tutor-->>MM: decline/timeout
MM->>MM: try next candidate (max N attempts)
MM-->>Student: (optional) still matching… spinner
end
end
Behavior
- Filter → Propose → Accept loop with short TTL to keep UX snappy.
- Balance & cap pre-check avoids proposing rates the student can’t afford.
- Rate lock at acceptance (for the session intent), so no last-second changes.
- On accept, we create room, then require escrow deposit before join.
- If escrow fails, offer retry or reduce cap; the tutor can be auto-released.
Edge cases & timeouts
- No tutors → offer booking or waitlist ping.
- Tutor non-response → retry next (max N attempts, e.g., 3).
- Race conditions (two students propose same tutor): first valid accept wins; others get retry.
Component 3B — Matchmaker (Booking Flow)
Purpose: let students book tutors in advance, ensure both confirm, and automatically trigger the escrow + call flow at the appointment time.
sequenceDiagram
autonumber
participant Student as Student dApp
participant MM as Matchmaker API
participant Tutor as Tutor dApp
participant Cal as Calendar / Scheduler
participant Escrow as Escrow Contract
participant Huddle as Huddle01
participant Notif as Notification Service
Student->>MM: requestBooking({tutorId, dateTime, duration, rateCap})
MM->>Tutor: proposeSlot({dateTime, duration, rateCap})
alt tutor accepts
Tutor-->>MM: accept
MM->>Cal: createEvent({student, tutor, time, duration})
Cal-->>MM: eventId
MM->>Notif: sendConfirmation(emails/push to both)
Notif-->>Student: booking confirmed
Notif-->>Tutor: booking confirmed
else tutor declines
Tutor-->>MM: decline
MM-->>Student: suggest new slot / other tutors
Note over MM: END (no booking)
end
%% Day-of-Session automation
Notif->>Student: reminder T-30 min
Notif->>Tutor: reminder T-30 min
Cal->>MM: trigger start window (T-5 min)
MM->>Huddle: createRoomToken(eventId)
MM->>Escrow: preAuthSession({student,tutor,rate,durationCap})
Escrow-->>MM: sessionIntent ok
MM-->>Student: joinLink + depositPrompt
Student->>Escrow: deposit(cap)
Student->>Huddle: join(roomToken)
Tutor->>Huddle: join(roomToken)
Note over Huddle: handoff → Heartbeat & Escrow components
Behavior Summary
Phase | Who | Action |
---|---|---|
Proposal | Student -> Tutor | request slot (date, duration, rateCap) |
Confirmation | Tutor | accept/decline → calendar entry |
Reminders | Notification service | 30 min & 5 min pre-session |
Pre-fund | Student | deposits cap before start |
Session Start | System | opens Huddle room + Escrow intent |
Live Call | Both | proceeds under Heartbeat + Auto-Stop rules |
Key Properties
- Double opt-in: prevents ghost bookings.
- Pre-authorization: ensures funds exist before the room opens.
- Automation: Cal/Notif triggers prevent manual coordination.
- Handoff: identical to roulette after join → no duplicate logic.
Component 4 — Credentials & Ratings
Purpose: after every session, issue a verifiable proof of participation and update both participants’ reputations. for vetted tutors, issue a Soul-Bound Token (SBT) to unlock credential-gated rooms.
sequenceDiagram
autonumber
participant Student as Student dApp
participant Tutor as Tutor dApp
participant ESC as Escrow Contract
participant POAP as POAP API / Mint Contract
participant DAO as Vetting DAO / SBT Issuer
participant IPFS as Ratings Storage
ESC-->>Student: sessionSettled(eventId)
Student->>POAP: mintPOAP(eventId, sig)
POAP-->>Student: tokenId (POAP NFT)
Note over Student,POAP: proof of session = credential #1
Student->>IPFS: uploadRating({tutorId, score, text})
IPFS-->>Student: cid
Student->>ESC: recordRatingHash(cid)
ESC-->>Tutor: reputationUpdate(cid)
alt DAO vetting process
Tutor->>DAO: applyForCredential({proofs, ratings})
DAO-->>Tutor: verify + mint SBT
Tutor->>SBT: hold soul-bound credential
end
Credential Types
Type | Purpose | Who mints | Transferable | Example |
---|---|---|---|---|
POAP | Proof of attendance / completion | automatic API | ✅ | Student finishes a lesson |
SBT (Tutor Credential) | DAO-vetted qualification | DAO governance | ❌ | “Certified Spanish native speaker” |
Rating Record | Session feedback | student → IPFS + hash on-chain | n/a | 5 stars, comment |
Logic Highlights
- Automatic POAP: triggered by sessionSettled; minimal friction.
- Reputation anchor: each rating = IPFS CID + hash on-chain → verifiable, append-only.
- DAO vetting: a DAO multisig reviews proofs/ratings → issues an SBT NFT.
- Access control: smart-contract rooms or filters check balanceOf(SBT) > 0.
Component 5 — DAO Vetting Flow (Tutor Credential SBT)
Component 6 — Streaming Payments with Dynamic Top-Up
Goals
- Pay exactly per second while the call is live.
- No fixed cap; student keeps talking while funded.
- Top up during the call (adds balance; stream continues).
- Auto-stop on disconnect without needing the student’s tx.
Key Pieces
- Streaming protocol: Superfluid CFA (or Sablier v2 per-second).
- Controller contract: has FlowOperator perms (Superfluid) / broker role (Sablier) for the student → can start/stop/update flows.
- Heartbeat service + Relayer: listens to Huddle01 events; calls controller to stop the stream on drop.
- Top-ups: student upgrades/supplies more tokens (can be gasless via Paymaster/AA if you want).
sequenceDiagram
autonumber
participant Student as Student dApp
participant Controller as Controller (Flow Operator)
participant Stream as Streaming Protocol (e.g., Superfluid)
participant Heartbeat as Heartbeat Service
participant Relayer as Relayer Bot
participant Tutor as Tutor Wallet
Note over Student: One-time: grant FlowOperator perms to Controller
Student->>Controller: startSession(tutor, ratePerSec)
Controller->>Stream: createFlow(student -> tutor, ratePerSec)
Stream-->>Tutor: tokens stream per-second
Note over Student,Stream: Student must hold/upgrade tokens as balance
par During call
loop Every few seconds
Student->>Stream: topUp(amount) %% upgrade/mint to stream balance
Stream-->>Student: balance updated
end
and Heartbeat monitoring
Heartbeat-->>Relayer: disconnect OR balance low
Relayer->>Controller: stopSession(sessionId)
Controller->>Stream: deleteFlow(student -> tutor)
end
alt Student ends manually
Student->>Controller: stopSession()
Controller->>Stream: deleteFlow(student -> tutor)
Note over Controller,Stream: stream deleted → payment stops instantly
end
Behavior Summary
- Start: Student clicks “Start Session” → controller creates a per-second stream at the tutor’s rate.
- During call:
- The student’s streaming balance decreases; they can top up anytime (send/upgrade more tokens).
- UI shows time remaining at current balance; warns early.
- Disconnect or end: Heartbeat/RTC event → relayer calls controller → stream deleted immediately.
- Billing: Exact, continuous—no settlement step needed. The tutor’s wallet accrues in real time.
Why this meets your constraints
- No cap UX: the conversation can continue indefinitely, as long as funds remain.
- Top-up live: student can add funds mid-call (even made gasless later).
- No post-drop paying: stream is deleted by the controller when the heartbeat detects a drop—no student tx needed.
- Tutor sets precise rate: store rate in tokens/sec, show per-minute in UI.
Minimal Contract Surface (pseudocode, not Solidity)
- grantOperator(student → controller, maxRate) — one-time user consent.
- startSession(student, tutor, ratePerSec) → create flow (enforce ratePerSec <= maxRate).
- stopSession(sessionId) → delete flow (callable by student, tutor, or relayer).
- (Optional) updateRate(newRatePerSec) during call if both sides accept.
Superfluid specifics: student grants the Controller FlowOperator permissions (create/update/delete flows) scoped by allowances; Controller uses CFAv1 to manage the stream. Sablier v2: use a broker/mediator stream that the Controller can cancel.
Top-Up UX Notes
- Warn at thresholds (e.g., 10 min, 3 min remaining at current rate).
- Provide one-click top-up (send/upgrade stablecoin) and consider a gasless paymaster.
- If balance dips below protocol buffer, pause or delete flow before liquidation.
Failure / Edge Cases
Case | Outcome |
---|---|
Student wallet runs out mid-call | Stream pauses/stops; UI prompts top-up; call can continue without billing (or you auto-end call). |
Student hard disconnects | Heartbeat → Relayer → Controller deletes stream immediately. |
Tutor disconnects | Same path; stream stops; student not charged further. |
Controller down | Worst case: stream continues until student manually stops; mitigate with redundant relayers/cron. |
Architecture (High-Level)
Diagram
Learner / Contributor (Wallet)
│
▼
Frontend (Next.js / React)
│
┌──────────┼───────────────────────────────┐
│ │ │
▼ ▼ ▼
Wallet Huddle01 SDK Credential Mint
Connect (Live Sessions) (POAP or minimal ERC-721)
(wagmi/viem) │ │
└───────────► Completion Signal │
│
▼
Reward Reminder / Claim payment(testnet)
Components
- Frontend: Next.js + wagmi/viem for wallet connect.
- Video: Huddle01 for low-latency, wallet-aware live sessions (optionally token-gated).
- Credentials: POAP (or minimal ERC-721) as proof of completion/participation.
- Payment: Streaming payment should be terminated upon session completion
- Serverless (optional for MVP): simple scheduling/metadata endpoints if needed.
Primary Flows
- Join & Learn
- Connect wallet → view schedule → join live Huddle01 session.
- Verify & Credential
- Host marks completion → participant mints POAP/credential.
- Reward (MVP)
- Display mock reward, tutor should receive payment
Notes & Constraints
- MVP: minimize backend; prefer client-side + third-party SDKs.
- Abuse prevention (post-MVP): host attestation, time-in-session checks, or allowlist for early pilots.
- Scalability: start single language (Spanish), design flows to be language-agnostic.
Future Integrations (Post-Hackathon)
- Chainlink: attestations / automation for scheduled tasks.
- Reputation: simple on-chain badges → aggregated contributor profile.
- Storage: IPFS/Arweave for session metadata or recordings if needed.