Find local happy hours and earn punch-card rewards at your favorite venues.
Hour is the worlds best happy hour discovery app: it helps people find what’s on near them right now, and helps venues turn that foot traffic into repeat visits. What's new for ETHGlobal Lisbon 2026 is the loyalty punch-card program.
For everyone, Hour replaces endless Googling with a living map of bars and restaurants, deal lists, live countdowns to when happy hour starts and ends, favourites, and a swipe-friendly browse flow. You open the app, see what’s happening nearby, and pick a spot without hunting Instagram stories or outdated blog posts. AI-assisted discovery keeps those listings fresh so the deals you see stay current.
For venue owners, Hour is a channel to people already looking for their next drink special. Venues can list for free, upgrade to verified pages, and use analytics and promotion to get in front of nearby searchers. On top of that, they can run a simple punch-card loyalty program: set how many visits earn a reward (for example, five punches for a free round), and opt the program on or off.
The loyalty loop is deliberately low-friction. Guests earn punches by photographing a receipt at the venue; when a card fills up, they unlock a reward and show a QR code that staff scan to redeem it. Cards reset for the next cycle after redemption, so regulars keep collecting without starting over from scratch. The result is one product with two jobs: help people find the best happy hours nearby, and help venues reward the customers who keep coming back.
FYI Hour is an iOS app, meaning it's only available to users who have iPhones (sorry Android users :c).
IMPORTANT REPO NOTES
This is a Continuity / Ship-a-Feature build on an existing private product (Hour), not a greenfield repo. Like other Continuity projects, the production code could not be forked as the submission root because of an existing Google Cloud CI/CD pipeline, so the hackathon work lives as two open-source packages consumed as git submodules: hour-rewards-sdk (Python/SQLModel) and hour-rewards-ui (React Native). To be clear about track fit: the deliverable is a shipped consumer product that real people use on their phones.
The legacy Hour stack stays the host: Expo mobile app (react-native), FastAPI + SQLAlchemy/SQLModel API, Postgres, venue-owner dashboard. Happy-hour discovery is background; the feature we shipped is the venue punch-card rewards loop end to end.
Background
Hour already had users, venues, owners, receipt OCR (Azure Document Intelligence), and a mobile/dashboard surface. For Lisbon we added a loyalty layer on top of that base: venues opt into a reward program, guests earn punches by photographing a receipt, a full card unlocks a short-lived QR code, and staff redeem it in person. The schema, rules, 0G verification, and Hedera ledger live in the SDK; camera, stamp animations, QR display/scan, and proof UI live in the mobile UI package. Auth, "who owns this venue," and OCR credentials stay in the host, and the packages never import the app.
0G Compute: turning a receipt into a punch
Punches are not stamped by staff. After Azure reads the photo to text, judgement runs on 0G Compute: an OpenAI-compatible router whose nodes run in a TDX enclave and sign every response. We ask a small multimodal model whether the OCR is a real receipt for this venue (name/address loaded from our venues row, never client-supplied), with a total and a date or receipt number so visits are distinguishable. Below a confidence floor the submission is refused; the only answer to a blurry photo is a better photo. I skipped implementing some human-review process for the hackathon due to time constraints. Privacy falls out of the design rather than being bolted on: the photo is read once and dropped, never persisted, and what leaves our server for inference is receipt text judged inside an enclave, with the punch keeping only a verdict, a hash, and the attestation.
What made 0G worth using over a plain LLM API is that every approved punch keeps a checkable attestation: ZG-Res-Key and the provider address land on the punch_event, and with TEE verification on we join the response signature to the node's quote so zg_tee_verified means an attested enclave served that inference. Anyone can re-fetch the signature later from the request id with no API key, and the app exposes exactly that: tapping any punch in a card's history opens a proof sheet that re-reads the signature and enclave signer live, so a customer can audit why their own punch was granted. Verifiability is mainly for a hackathon demo-facing feature here, not a hidden backend detail. We also do not trust the model alone on venue identity: after an approval, a deterministic venue_name_in_text() guard can only downgrade a yes to venue_mismatch, which matters when OCR mangles headers ("VIP BILLIARDS" vs "VIP Billiards Bloor"). Verified receipts are single-use per venue via a dedupe hash (receipt number, or date+total), so the same slip cannot punch twice or be claimed from a second account. (Note on this, multiple users can claim the same receipt as per our demo, but this is not planned production usage).
What we satisfy on the 0G track: a working, demoable product (TestFlight iOS build, real GCP-deployed backend, not just a repo); 0G Compute used for every inference in the critical path, with the response key, provider address and TEE join persisted per punch as proof; public repos with README and setup instructions; and scripts/zg_demo.py, which runs a real verdict against the router from a receipt's text with no database involved, so a judge can reproduce the inference in one command. We deployed no contracts, so there are no contract addresses to report: 0G here is private verifiable compute inside a consumer product, and Storage, Agentic IDs and 0G Chain are deliberately out of scope for this hackathon rather than half-integrated.
Hedera: punch cards as native assets (no Solidity)
Overview of services used: venue opt-in → HTS TokenCreate + HCS TopicCreate first punch → AccountCreate + TokenMint + NFT transfer each punch → HCS TopicMessageSubmit + TokenUpdateNfts reward claim → HCS TopicMessageSubmit + TokenUpdateNfts
On Hedera we stayed SDK-only with hiero-sdk-python, and used three native services rather than the two required: HTS for the card, HCS for the audit trail, and the account service for custodial user accounts. Zero Solidity, zero contracts, zero EVM calls. When a venue opts in we create an NFT collection plus a per-venue topic. On a user's first verified punch we create a custodial Hedera account with AccountCreateTransaction (key Fernet-encrypted server-side, guests sign in with Google/Apple and never see a wallet), mint one NFT serial for that card, and transfer it. Every punch and every redemption submits an HCS message and bumps NFT metadata via HIP-657's metadata key. Cards are never reminted per cycle; redeeming increments cycle_number in Postgres and rewrites the same serial's metadata, so one durable card holds the venue's whole history.
The 100-byte onchain metadata cap forced a deliberate design: metadata is a short URI into our API (…/nft/{card_id}?v={cycle}-{count}), and we serve live HIP-412 JSON from the current card row. Versioning the query string means each state change is its own signed update, not a silent edit behind a stable pointer. Topic messages carry a salted hash of the user id plus the receipt hash and 0G attestation ids, enough to prove why a punch was granted without publishing what anyone bought. The proof sheet then closes the loop through the Mirror Node REST API: it fetches that exact topic message back by sequence number, decodes it, and shows the consensus timestamp, payer account and running hash next to HashScan links for the topic, the NFT serial, the metadata transaction and the holding account. The user is reading the network's copy, not ours. Critically, Hedera never gates the product path: ledger calls are best-effort; a testnet blip leaves hedera_* database columns null and the punch still succeeds. Ids are committed as soon as they exist so a later HederaLedger.reconcile() can finish mints, transfers, and topic backfills without inventing history, republishing missed events with the timestamp they were earned at and a backfill flag, which is why consumers deduplicate on the event uuid every message carries.
What we satisfy on the Hedera bounty, including the optional enhancements: Python SDK only with no smart contracts; four native surfaces (HTS, HCS, account creation, Mirror Node REST) against a two-service minimum; a coherent end-to-end user experience where a guest photographs a receipt in a real iOS app and staff scan a QR at the till, with no wallet, seed phrase or gas anywhere in the flow; Mirror Node REST integration used for in-app data integrity rather than a debugging aside; and creative HCS use as a privacy-preserving audit log that carries hashes and attestations instead of purchase data. scripts/hedera_demo.py walks one card through its entire lifecycle against testnet with no database, printing a HashScan link for every transaction, so the whole integration is verifiable in one command.
How it's wired together
End to end: mobile PunchCameraModal uploads bytes, host OCR reads them, RewardService.submit_receipt asks 0G for a verdict, Postgres records the punch, Hedera mints or updates the card and publishes the event, and the app plays the stamp animation and offers the proof sheet (0G attestation plus HashScan and Mirror Node links). When the card is full, RewardQrCodeModal draws a TTL'd hour://redeem/… payload locally, because a live token has no business travelling to a third-party QR generator and a code that needs a network to be shown is a code that fails at the till. The owner's QrRedemptionScanModal rejects a wrong-venue code offline, since the same payload parser lives in both the Python SDK and the RN package, then the API completes redemption and publishes the claim. I manually flip reward programs on for venues from my CLI; FastAPI routes only do authz and hand work to the SDK. Optional extras (hedera, zg) and separate Alembic ops mean punch cards run with neither sponsor configured in tests, and light up when keys are present, which is how we kept a shippable loyalty product while making the sponsor layers real, auditable, and fail-open.
Something particularly hacky that I did was how I used the nft metadata. Hedera NFT metadata is capped at 100 bytes (HIP-657), so we couldn’t put punch-card state onchain. We mint a versioned URI (?v={cycle}-{count}) that resolves to live HIP-412 JSON from our API, then rewrite that URI on every punch/redeem via the token’s metadata key. One durable NFT accrues a venue’s whole history; wallets that cache metadata still see progress move, while the actual audit trail of each punch lives on the venue’s HCS topic.
All of this to benefit one thing, nothing the user buys or actually gives me in their receipt is stored, we simply have a decentralized notary of all verified events, processed by independant and protected AI execution. Only a trail and record of what was legit when it was scanned.
HOW TO TEST
Prerequisites 0. Have 2 iPhones available (one as "user", and one as "owner")
iPhone A: Getting a punch on your punch card 0a. Make sure you are on Lisbon location (top left on home page) 0b. Search for "Kazi Eatery", and click it
iPhone B: Scan to redeem QR code rewards 0. Follow steps 0 above
NOTE ON DEMO: Redeeming during judging: Normally "Scan QR Code" on a venue is limited to that venue's verified owners: staff scan the customer's code and hand over the reward. So judges can walk the whole loop without us provisioning owner accounts, this build lets any signed-in user open a venue's scanner. You need two iPhones, one person opens their punch card and taps "Show QR code", the other opens the same venue and scans it. Every other check still applies: the code must belong to that venue, be unexpired, unused, and from the card's current cycle. It's a demo-only relaxation behind a server flag and gets switched off after judging; the redemption is simply filed with no staff member attributed.

