Skip to content

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

  • FilterProposeAccept 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

  1. Join & Learn
  2. Connect wallet → view schedule → join live Huddle01 session.
  3. Verify & Credential
  4. Host marks completion → participant mints POAP/credential.
  5. Reward (MVP)
  6. 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.