The café that doesn't exist
It sells coffee it cannot ship, on chains that cost nothing to use. Every counter in here is a real piece of the stack wearing an apron — including the one in the corner that has never been switched on.

THE BAR
An ordinary coffee shop, built properly
Catalog, cart, checkout, accounts, orders — the part with no blockchain in it at all. A Blazor Web App on .NET 10 with interactive server rendering, split into the five projects Clean Architecture asks for, and it would still work with every wallet in the building unplugged.
- RUNTIME
- .NET 10 · Blazor Server
- DATA
- EF Core · PostgreSQL · Identity
- SHAPE
- Domain → Application → Infrastructure → Web
01
The Bar
One page, twelve moving parts
/products is the honest half of this project: a
catalog with no wallet anywhere in it. It is worth pulling apart
because everything else in the room hangs off the same spine —
a routable Blazor page, a service interface, a repository, EF
Core, Postgres.
Component tree · Components/Pages/Products.razor
- Products.razor
- HeadContent
- CatalogHero
- FilterSidebar
- ProductCard
- RatingStars
- LucideIcon
Where a card's data actually comes from
- ProductCard Components/Storefront
- CatalogProductCardMapper DTO → view model
- IProductService Application layer
- IProductRepository Infrastructure
- Postgres EF Core · products
The page never touches a DbContext. It injects
IProductService, which is the only thing the Web
project is allowed to know about — the Domain entity behind it
carries name, slug, description, price, stock, image, category,
origin, roast level, process and an active flag, and the
sidebar's facets are all derived from those columns at runtime
rather than hardcoded.
- Prerender
-
The product list is stashed with
PersistentComponentStateduring the server render and read back when the circuit connects, so the catalog is not fetched twice and the grid does not flash empty on hydration. - Filtering
-
Category, origin, roast and process are held as
HashSet<string>on the component; price is a floor/ceiling pair derived from the loaded set. Filtering and paging both happen in memory, over a catalog small enough that a round trip per keystroke would be the slower design. - Styling
-
Two systems, deliberately: Tailwind utilities for the catalog
grid, component-scoped
.razor.cssfor everything with a name. The pixel wing you are looking at right now is the second kind.
02
The Roaster
What keeps the heat on
Everything below is Bicep in infra/, deployed to one
resource group on Azure Container Apps. No portal clicking: a
GitHub Actions workflow federates into Azure over OIDC, so there
is no cloud credential stored in the repository at all.
Resource group · thiscafeteria-prod-rg · westus
Container Apps Environment — Consumption
web
0.5 vCPU · 1 GiB
min 1 · max 2
ingress :8080
worker
0.25 vCPU · 0.5 GiB
min 1 · max 1
no ingress
sepolia-bundler
0.5 vCPU · 1 GiB
internal only :4338
not deployed → 06
Postgres Flexible
Burstable B1ms · 32 GB
Storage · Blob
receipts container
Service Bus
wallet-status
order-processing
Key Vault
every secret env var
Communication Services
transactional email
Container Registry
web · worker · rundler
- Two identities
- The apps run as a user-assigned managed identity holding AcrPull, Key Vault Secrets User, Storage Blob Data Contributor and Service Bus Sender/Receiver. CI/CD is a separate identity, federated to GitHub over OIDC, holding AcrPush and Container Apps Contributor. Neither can do the other's job.
- The queue
- Checkout writes an order-processing message; the Worker — its own container, no ingress, one replica — drains it and updates Postgres. That split is why an order survives a web replica restart mid-checkout.
- Why westus
- Not a preference. This is an Azure for Students subscription: eastus2 is blocked for both Postgres Flexible Server and Container Apps environments by resource-specific capacity restrictions, and mexicocentral does not offer Container Apps at all. westus was the region that actually worked end to end.
03
The Till
Nine networks, three of them real
The registry ships nine chain definitions and every one of them
is disabled. A chain becomes visible only when a committed
deployment manifest under deployments/ proves its
contracts exist — the manifest is the source of truth, and
ChainRegistry.Validate rejects one that claims a
capability whose contract address is missing.
Chain definitions · live ones carry a manifest
Ethereum Sepolia
11155111
manifest present
BNB Testnet
97
manifest present
Solana Devnet
Anchor program
manifest present
Hedera Testnet
no manifest
Avalanche Fuji
no manifest
Linea Sepolia
no manifest
Base Sepolia
no manifest
Monad Testnet
no manifest
Arbitrum Sepolia
no manifest
What Sepolia's manifest actually addresses
- CAFE
- 0x15DbED39271D2788a9Be63ffB34C8E2DdED8754A
- COFFEE
- 0x4056E7F5FD1584C3db6223c9483761Dcb30Bf21C
- Liquid vault
- 0x0a7b7d7eb39ca835ef5854875bb97885d23d1df3
- EntryPoint 0.7
- 0x7d75859d1e2be07b0c18c0ef3dd062b69bcc4217
- ERC-8004 registry
- 0x45d9e43a0ce744eaa29982a31a5b5ad48d1d6106
- ERC-7683 resolver
- 0xb5bf1af688316d724f1fec07b6867c770cab82e6
- ERC-8183 escrow
- 0x44f97d09090e27962d2665f0b95a753273a89350
Compiled with solc 0.8.24, 200 optimizer runs, viaIR — recorded in the manifest alongside the deploy block, so a redeploy that changes the compiler is visible in the diff rather than silent.
- The gate
BlockchainManifestLoaderreads the manifest,ChainRegistry.Validaterejects an inconsistent one, andChainsControllerfilters/api/chainsonEnabled. Both chain selectors read that endpoint, so an unfinished connection cannot be advertised by accident.- Still off
marketplacePaymentneeds an escrow or the legacy pool deployed and addressed;legacyExitneeds the legacy pool. Neither committed manifest declares them, so both stay dark on every chain.
04
The Cellar
A receipt you can carry
CafeLiquidStakingVault.sol is an ERC-4626 vault —
plus Ownable2Step, Pausable and
ReentrancyGuard. You deposit CAFE, you get stCAFE
shares, and COFFEE rewards stream to whoever holds those shares.
Being ERC-4626 is the whole point: the receipt is an ordinary
ERC-20 that can move without unwinding the position.
Deposit → hold → redeem
- CAFE the asset
- deposit() or mint()
- stCAFE ERC-4626 shares
- redeem() or withdraw()
- CAFE back shares burned
Rewards run on a separate rail. The owner calls
notifyRewardAmount(amount, duration) to open a
streaming period; rewardPerShare() accrues against
it, earned(account) reads a holder's balance, and
claimRewards() pays out in COFFEE without touching
the staked CAFE.
- Transfers are honest
-
The vault overrides
_updateso a stCAFE transfer settles rewards for both sides before the balance moves. Sending your receipt away hands over the future stream, not the rewards you already earned. - Two chains, one shape
- The same deposit/redeem/claim vocabulary runs on EVM and on a Solana Anchor program, and both Web and Worker reconcile every recorded operation independently, using chain-qualified identities and cursors so a Sepolia event can never be mistaken for a devnet one.
- What it is not
- This is liquid staking of the application's own CAFE token. It is not validator staking of ETH, SOL, BNB or anybody else's native currency, and nothing here earns real yield.
05
The Back Room
Agents that have to ask
The procurement lab hands escrowed test missions to agents running on ERC-4337 smart accounts. The interesting constraint is the one it refuses to relax: every action is signed by your wallet. There is no unattended mode to turn on.
A mission, end to end
- Agent registeredERC-8004
- Permission grantedscoped epoch
- Funds escrowedERC-8183
- Intent resolvedERC-7683
- UserOperationwallet-signed
- Settledreconciled twice
- Smart accounts
- EntryPoint 0.7, a simple account factory, and a verifying paymaster that can sponsor gas. Sponsorship is metered and revocable — a reverted sponsored operation still costs the paymaster, so it is counted against a budget rather than billed as gas.
- The crew upstairs
- The robots wandering the homepage are the same idea at play without the money: a 194-parameter policy trained offline, then run per frame in the browser against the exact physics the trainer used. Nobody choreographed them.
- Missing piece
- Everything above assumes something will accept a UserOperation. Today that is a public bundler. The thing meant to replace it is sitting in the corner of the room, switched off — section 06.
06
The Dark Node NOT RUNNING
Provisioned, wired, never switched on
There is a fourth Container App in infra/main.bicep
that has never existed in Azure. It is a private Rundler
instance — a self-hosted ERC-4337 bundler for the lab — and it is
behind a Bicep conditional that is still false. It
is in this room because a thing you have designed and not yet
turned on is part of the truth about a system.
thiscafeteria-prod-sepolia-bundler
- Image
- Rundler v0.11.0, built from
contracts/evm/rundler/Dockerfile - Ingress
- Internal only, port 4338 — an unauthenticated bundler RPC never goes public
- Shape
- 0.5 vCPU · 1 GiB · one always-on replica, no scale-to-zero
- Serves
- EntryPoint 0.7 at
0x7d75…4217, the one the lab already uses - Secrets
- Node RPC URL and signer key, both read from Key Vault at start
- Switch
enableSepoliaBundler = falseininfra/main.bicepparam
Two things stand between it and a green light
A tracer-capable Sepolia RPC
Rundler's safe mode validates a UserOperation with
debug_traceCallusing a custom JavaScript tracer. Ordinary public Sepolia endpoints do not offer that, so this needs a provider endpoint that does — and that credential is not going in the repository.A dedicated funded signer
The bundler needs its own Sepolia-funded beneficiary key, explicitly not the contract deployer key. One compromised bundler should not be able to touch anything that was deployed.
Both land in Key Vault, then Bicep is redeployed with
enableSepoliaBundler=true, and the final proof runs
from a one-off workload inside the same Container Apps
environment against the internal FQDN — not by making the RPC
public for the length of a test.
