Slipstream: Shipping a Verifiable Real-Time Platform in 48 Hours
How a four-person team built and deployed a Next.js + FastAPI + WebSocket platform with on-chain decision logging, end to end, in a weekend, and walked out with 1st place at Encode AI Hackathon.
Most hackathon MVPs are a localhost demo and a Figma file. Judges politely nod and move on. Slipstream went the other way. We decided on hour one that the only thing we would show on stage was a live URL, a live WebSocket feed, and a verifier the judges could query themselves. Forty-eight hours later it shipped, and it won.

The problem worth winning on
Picking the problem is half the hackathon. We wanted one where the on-chain layer was a feature, not a sticker. Logging decisions where someone has an incentive to later claim 'the system did not actually say that' fits. Customer support escalations, automated trading triggers, AI-assisted moderation calls. The tamper-evident log is the product, not the marketing.
Problem: real-time decisions made by an opaque service are unverifiable after the fact. Solution: stream decisions live to the operator, hash each one into an append-only on-chain log, give anyone the ability to re-verify the hash. Approach: smallest possible surface, real deployment, no demo mode.
Architecture in one breath
A Next.js frontend on Vercel, talking to a FastAPI backend behind a Cloudflare Tunnel, streaming live state over WebSockets, with a Solidity contract on a testnet anchoring the hash of every decision the backend emits. One Postgres row per decision, one on-chain transaction per decision, one WebSocket frame per decision. Same identifier across all three.
Browser <--WSS--> Next.js (Vercel) <--WSS--> FastAPI (Docker + Cloudflare Tunnel)
|
+--> Postgres (decisions)
+--> Solidity contract (recordDecision)Why this stack, not the obvious one
FastAPI over Node for the backend because Pydantic gives you a typed contract for free, and at hackathon speed the cost of a malformed payload at 2am is the entire night. WebSockets over polling because the product is a live feed; polling would have been a lie about what the system does. Cloudflare Tunnel over a cloud VM because it gives you TLS and a public URL in two minutes with zero DNS work, which matters when your runway is hours. Solidity, not because the business logic belongs on-chain, but because we needed exactly one verifiable primitive: an append-only log of hashes that we do not control.
The WebSocket contract
One envelope shape, enforced on both ends. Pydantic on the server, a generated TypeScript type on the client. The contract was written before either side existed, which is the only reason the integration step took twenty minutes instead of twenty hours.
# server: app/models.py
class DecisionFrame(BaseModel):
id: str # uuid, matches on-chain log
ts: int # unix ms
kind: Literal['decision', 'heartbeat', 'error']
payload: dict
hash: str # sha256 of canonical payload
// client: src/types/stream.ts
export type DecisionFrame = {
id: string; ts: number;
kind: 'decision' | 'heartbeat' | 'error';
payload: Record<string, unknown>;
hash: string;
};Heartbeats every five seconds so the client knows the difference between 'quiet' and 'broken'. Errors are first-class frames, not HTTP 500s the client never sees, because over a WebSocket an unhandled exception just looks like silence.
The on-chain layer, kept small
One function. recordDecision(bytes32 hash) emits an event with the hash and a monotonically increasing index. That is it. No access control on writes from the backend signer, no business logic, no storage of the payload itself. The payload lives in Postgres; only its hash is anchored.
// contracts/Slipstream.sol
pragma solidity ^0.8.20;
contract Slipstream {
uint256 public count;
event DecisionRecorded(uint256 indexed idx, bytes32 hash, uint256 ts);
function recordDecision(bytes32 hash) external {
emit DecisionRecorded(count, hash, block.timestamp);
count += 1;
}
}Why so small. Every line of Solidity you write at a hackathon is a line you cannot audit by Sunday morning. The contract does one thing and the off-chain code does everything else. A judge can paste a payload into our verifier, see its hash, look up the index in the event log, and confirm it matches. That is the whole trust story, and it fits in three sentences.
Deployment under a clock
Docker Compose locally for parity, FastAPI exposed publicly via Cloudflare Tunnel (no firewall rules, no cert provisioning, no DNS panel), Next.js on Vercel pointed at the tunnel URL. The env wiring is the part that costs you a hackathon. We made one decision early: every secret lives in a single .env.example checked into the repo, and the real values live in 1Password. No 'wait, what is the websocket URL in staging' at 3am.
We lost twenty minutes to CORS. We always lose twenty minutes to CORS. The fix is to write the allowed origins as a list, not a regex, and to log the rejected origin server-side so you can see what the browser actually sent.
What 1st place actually rewarded
Three things, in order. A working live URL the judges hit from their own laptops. A verifier they could run against the on-chain log without our help. A three-minute story that fit the demo slot without skipping the technical claim. Capability is table stakes at this level. Legibility is what wins. If a judge has to take your word for it, you have already lost.
What I would change with a week instead of a weekend
Proper auth on the backend, not just a shared secret. A managed gateway in front of FastAPI so we can retire the tunnel and get real observability. Batching the on-chain writes (one tx per N decisions with a Merkle root) so gas stops being linear in volume. A replay tool that lets an operator scrub through historical WebSocket frames as if they were live, which is what you actually want during an incident review. None of that was wrong to skip in 48 hours. All of it would be wrong to skip in production.
At hackathon timescales, planning loses to shipping every time. The team that picks a small, verifiable claim and proves it live will beat the team with the better deck. Always.