|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import React, { useCallback, useEffect, useState } from "react"; |
| 4 | + |
| 5 | +// ─── Slide Data ───────────────────────────────────────────────────────────── |
| 6 | + |
| 7 | +type Slide = { |
| 8 | + label: string; |
| 9 | + title: string; |
| 10 | + bullets?: string[]; |
| 11 | + footnote?: string; |
| 12 | + image?: string; |
| 13 | + code?: string; |
| 14 | + variant?: "title" | "demo" | "default"; |
| 15 | +}; |
| 16 | + |
| 17 | +const slides: Slide[] = [ |
| 18 | + // ── Act 1: The Problem ── |
| 19 | + { |
| 20 | + label: "The Problem", |
| 21 | + title: "Smart contracts are too smart", |
| 22 | + image: "/slides/wojak.jpg", |
| 23 | + variant: "title", |
| 24 | + }, |
| 25 | + |
| 26 | + // ── Act 2: The Solution ── |
| 27 | + { |
| 28 | + label: "The Solution", |
| 29 | + title: "Formally defined transaction interpretation", |
| 30 | + bullets: [ |
| 31 | + "We extended the Verity smart contract language with an intent DSL", |
| 32 | + "Each contract function gets a formal spec that maps calldata to natural language", |
| 33 | + "The spec is written in Lean 4, so the mapping itself is provable", |
| 34 | + ], |
| 35 | + }, |
| 36 | + { |
| 37 | + label: "The Solution", |
| 38 | + title: "A scriptable, provable ERC-7730", |
| 39 | + bullets: [ |
| 40 | + "The DSL supports conditions, loops, predicates", |
| 41 | + "But evaluating this DSL is too heavy for an edge device like a Ledger", |
| 42 | + ], |
| 43 | + code: `intent approve(spender : address, amount : uint256) where |
| 44 | + when amount == maxUint256 => |
| 45 | + emit "Approve {spender} to spend unlimited USDC" |
| 46 | + otherwise => |
| 47 | + emit "Approve {spender} to spend {amount:fixed decimals} USDC"`, |
| 48 | + }, |
| 49 | + { |
| 50 | + label: "The Solution", |
| 51 | + title: "So we built a ZK circuit compiler", |
| 52 | + bullets: [ |
| 53 | + "The Verity compiler generates a Groth16 circuit from the same Lean spec", |
| 54 | + "The circuit proves: this natural language string is the correct interpretation of this calldata", |
| 55 | + "A hardware wallet just verifies a proof, 4 pairings on BLS12-381", |
| 56 | + ], |
| 57 | + }, |
| 58 | + |
| 59 | + // ── Act 3: How It Works ── |
| 60 | + { |
| 61 | + label: "How It Works", |
| 62 | + title: "Step 1: Spec lookup", |
| 63 | + bullets: [ |
| 64 | + "The contract address is matched against the veryclear.eth ENS registry", |
| 65 | + "Each entry maps a contract to a compiled spec (e.g. ERC20, UniswapV2)", |
| 66 | + "The registry is on-chain, verifiable by anyone", |
| 67 | + ], |
| 68 | + }, |
| 69 | + { |
| 70 | + label: "How It Works", |
| 71 | + title: "Step 2: Function identification", |
| 72 | + bullets: [ |
| 73 | + "The 4-byte selector identifies which function is being called", |
| 74 | + "0xa9059cbb = transfer, 0x095ea7b3 = approve", |
| 75 | + "The spec binding maps each selector to an intent function", |
| 76 | + ], |
| 77 | + }, |
| 78 | + { |
| 79 | + label: "How It Works", |
| 80 | + title: "Step 3: Calldata decoding", |
| 81 | + bullets: [ |
| 82 | + "Raw bytes are decoded into typed parameters using the ABI", |
| 83 | + "uint256 values split into 128-bit limbs to fit the ZK field", |
| 84 | + "Addresses, booleans, arrays each have their own decoding logic", |
| 85 | + ], |
| 86 | + }, |
| 87 | + { |
| 88 | + label: "How It Works", |
| 89 | + title: "Step 4: Intent evaluation", |
| 90 | + bullets: [ |
| 91 | + "The DSL program selects a template based on parameter values", |
| 92 | + 'amount == MAX_UINT256? "Approve unlimited" : "Approve {amount}"', |
| 93 | + "Template index matches the circuit output for proof consistency", |
| 94 | + ], |
| 95 | + }, |
| 96 | + { |
| 97 | + label: "How It Works", |
| 98 | + title: "Step 5: Address resolution", |
| 99 | + bullets: [ |
| 100 | + "Raw addresses in the template are resolved against the spec registry", |
| 101 | + "0x7a250d... becomes UniswapV2Router", |
| 102 | + "Unknown addresses stay truncated (0xAbCd...1234)", |
| 103 | + ], |
| 104 | + }, |
| 105 | + { |
| 106 | + label: "How It Works", |
| 107 | + title: "Step 6: Proof generation", |
| 108 | + bullets: [ |
| 109 | + "The browser generates a Groth16 proof over BLS12-381", |
| 110 | + "Circuit computes Poseidon(selector, params) and Poseidon(templateId, holes) internally", |
| 111 | + "Proof size: ~400 bytes, generation: ~500ms in browser", |
| 112 | + ], |
| 113 | + }, |
| 114 | + { |
| 115 | + label: "How It Works", |
| 116 | + title: "Step 7: Hardware verification", |
| 117 | + bullets: [ |
| 118 | + "The proof + verification key + intent text are sent to the Ledger Nano S+", |
| 119 | + "The Ledger displays the intent, user confirms with physical buttons", |
| 120 | + "On approve: 4-pairing Groth16 check on ARM Cortex-M35P secure element", |
| 121 | + ], |
| 122 | + footnote: "The browser generates, the hardware verifies. Zero trust required.", |
| 123 | + }, |
| 124 | + |
| 125 | + // ── Closing ── |
| 126 | + { |
| 127 | + label: "Demo", |
| 128 | + title: "explain.md/clear-signing", |
| 129 | + variant: "demo", |
| 130 | + }, |
| 131 | +]; |
| 132 | + |
| 133 | +// ─── Slide Component ──────────────────────────────────────────────────────── |
| 134 | + |
| 135 | +function highlightCode(line: string): React.ReactNode { |
| 136 | + if (line.trim() === "") return "\u00A0"; |
| 137 | + // Simple Lean-like syntax highlighting |
| 138 | + return line |
| 139 | + .replace( |
| 140 | + /\b(intent|when|otherwise|emit|where)\b/g, |
| 141 | + '<kw>$1</kw>' |
| 142 | + ) |
| 143 | + .replace( |
| 144 | + /\b(address|uint256|bool)\b/g, |
| 145 | + '<ty>$1</ty>' |
| 146 | + ) |
| 147 | + .replace( |
| 148 | + /"([^"]*)"/g, |
| 149 | + '<str>"$1"</str>' |
| 150 | + ) |
| 151 | + .replace( |
| 152 | + /\b(maxUint256|decimals)\b/g, |
| 153 | + '<id>$1</id>' |
| 154 | + ) |
| 155 | + .split(/(<kw>.*?<\/kw>|<ty>.*?<\/ty>|<str>.*?<\/str>|<id>.*?<\/id>)/) |
| 156 | + .map((part, i) => { |
| 157 | + if (part.startsWith('<kw>')) return <span key={i} className="text-[#907aa9]">{part.slice(4, -5)}</span>; |
| 158 | + if (part.startsWith('<ty>')) return <span key={i} className="text-[#ea9d34]">{part.slice(4, -5)}</span>; |
| 159 | + if (part.startsWith('<str>')) return <span key={i} className="text-[#56949f]">{part.slice(5, -6)}</span>; |
| 160 | + if (part.startsWith('<id>')) return <span key={i} className="text-[#286983]">{part.slice(4, -5)}</span>; |
| 161 | + return part; |
| 162 | + }); |
| 163 | +} |
| 164 | + |
| 165 | +function SlideView({ slide }: { slide: Slide }) { |
| 166 | + if (slide.variant === "demo") { |
| 167 | + return ( |
| 168 | + <> |
| 169 | + <p className="mb-6 text-sm uppercase tracking-[0.3em] opacity-40"> |
| 170 | + {slide.label} |
| 171 | + </p> |
| 172 | + <h1 className="font-serif text-7xl italic">{slide.title}</h1> |
| 173 | + </> |
| 174 | + ); |
| 175 | + } |
| 176 | + |
| 177 | + return ( |
| 178 | + <> |
| 179 | + <p className="mb-6 text-sm uppercase tracking-[0.3em] opacity-40"> |
| 180 | + {slide.label} |
| 181 | + </p> |
| 182 | + <h1 |
| 183 | + className={`max-w-3xl text-center font-serif leading-tight ${ |
| 184 | + slide.variant === "title" ? "text-5xl" : "text-4xl" |
| 185 | + }`} |
| 186 | + > |
| 187 | + {slide.title} |
| 188 | + </h1> |
| 189 | + {slide.image && ( |
| 190 | + <img |
| 191 | + src={slide.image} |
| 192 | + alt="" |
| 193 | + className="mt-8 max-h-[40vh] rounded-lg" |
| 194 | + /> |
| 195 | + )} |
| 196 | + {slide.code && ( |
| 197 | + <pre className="mt-8 bg-[#faf4ed] rounded-lg px-6 py-5 text-[15px] leading-[1.7] font-mono max-w-2xl overflow-x-auto"> |
| 198 | + {slide.code.split("\n").map((line, i) => ( |
| 199 | + <div key={i}> |
| 200 | + {highlightCode(line)} |
| 201 | + </div> |
| 202 | + ))} |
| 203 | + </pre> |
| 204 | + )} |
| 205 | + {slide.bullets && ( |
| 206 | + <ul className="mt-10 max-w-2xl space-y-5 text-center font-serif text-xl leading-relaxed opacity-70"> |
| 207 | + {slide.bullets.map((b) => ( |
| 208 | + <li key={b}>{b}</li> |
| 209 | + ))} |
| 210 | + </ul> |
| 211 | + )} |
| 212 | + {slide.footnote && ( |
| 213 | + <p className="mt-12 max-w-xl text-center text-sm italic opacity-40"> |
| 214 | + {slide.footnote} |
| 215 | + </p> |
| 216 | + )} |
| 217 | + </> |
| 218 | + ); |
| 219 | +} |
| 220 | + |
| 221 | +// ─── Presentation ─────────────────────────────────────────────────────────── |
| 222 | + |
| 223 | +export default function SlidesPage() { |
| 224 | + const [current, setCurrent] = useState(0); |
| 225 | + |
| 226 | + const next = useCallback(() => { |
| 227 | + setCurrent((c) => Math.min(c + 1, slides.length - 1)); |
| 228 | + }, []); |
| 229 | + |
| 230 | + const prev = useCallback(() => { |
| 231 | + setCurrent((c) => Math.max(c - 1, 0)); |
| 232 | + }, []); |
| 233 | + |
| 234 | + useEffect(() => { |
| 235 | + function onKey(e: KeyboardEvent) { |
| 236 | + if (e.key === "ArrowRight" || e.key === " ") { |
| 237 | + e.preventDefault(); |
| 238 | + next(); |
| 239 | + } |
| 240 | + if (e.key === "ArrowLeft") { |
| 241 | + e.preventDefault(); |
| 242 | + prev(); |
| 243 | + } |
| 244 | + } |
| 245 | + window.addEventListener("keydown", onKey); |
| 246 | + return () => window.removeEventListener("keydown", onKey); |
| 247 | + }, [next, prev]); |
| 248 | + |
| 249 | + const slide = slides[current]; |
| 250 | + |
| 251 | + return ( |
| 252 | + <div |
| 253 | + className="flex h-screen w-screen select-none flex-col items-center justify-center px-12 cursor-pointer" |
| 254 | + onClick={next} |
| 255 | + > |
| 256 | + <SlideView slide={slide} /> |
| 257 | + |
| 258 | + {/* Slide indicator */} |
| 259 | + <div className="fixed bottom-8 flex gap-2"> |
| 260 | + {slides.map((_, i) => ( |
| 261 | + <div |
| 262 | + key={i} |
| 263 | + className={`h-1.5 w-1.5 rounded-full transition-opacity ${ |
| 264 | + i === current ? "bg-foreground opacity-60" : "bg-foreground opacity-15" |
| 265 | + }`} |
| 266 | + /> |
| 267 | + ))} |
| 268 | + </div> |
| 269 | + |
| 270 | + {/* Slide number */} |
| 271 | + <div className="fixed bottom-8 right-8 font-mono text-xs opacity-30"> |
| 272 | + {current + 1} / {slides.length} |
| 273 | + </div> |
| 274 | + </div> |
| 275 | + ); |
| 276 | +} |
0 commit comments