Captcha
A slide-to-verify captcha with no third party and no tracking. Sliding advances a hash chain and solves a proof-of-work, so a passing submit proves the page was really loaded and the work was really done.
mintCaptcha() seals a single-use token at SSR. Sliding the handle advances a hash chain one link per step, then solves a proof-of-work over the final link — so the
challenge only exists once the slide has actually run, and never appears in the page. verifyCaptcha() re-derives it server-side.
The submit button is not gated on the captcha here, so you can submit without solving it and watch the server reject you.
Submit twice (replay) verifies the same token twice in one action — the first call burns the nonce, so the second is a real replay. A failure carries a reason, shown under each message: solve the captcha first and you get replay, which the demo answers with its own copy. Leave it unsolved and every
probe-able failure — tampered, too fast, expired, bad proof-of-work — collapses to rejected behind one generic message, so a bot can't tell them apart.
<script lang="ts">
import CaptchaForm from './CaptchaForm.svelte';
import { files } from './files.ts';
import type { MintedCaptcha } from 'mochi-framework';
let { captcha }: { captcha: MintedCaptcha } = $props();
const sources = await loadSources(files);
</script>
<p>
<code>mintCaptcha()</code> seals a single-use token at SSR. Sliding the handle advances a hash chain one link per step, then solves a proof-of-work over the final link — so the
challenge only exists once the slide has actually run, and never appears in the page.
<code>verifyCaptcha()</code> re-derives it server-side.
</p>
<p>
The submit button is <strong>not</strong> gated on the captcha here, so you can submit without solving it and watch the server reject you.
</p>
<p>
<strong>Submit twice (replay)</strong> verifies the same token twice in one action — the first call burns the nonce, so the second is a real replay. A failure carries a
<code>reason</code>, shown under each message: solve the captcha first and you get <code>replay</code>, which the demo answers with its own copy. Leave it unsolved and every
probe-able failure — tampered, too fast, expired, bad proof-of-work — collapses to <code>rejected</code> behind one generic message, so a bot can't tell them apart.
</p>
<CaptchaForm mochi:hydrate {captcha} />
Styles
<style>
p {
margin: 0 0 0.5rem;
font-size: 0.9rem;
color: var(--text-muted);
}
code {
background: var(--code-bg);
color: var(--code-accent);
padding: 0.05rem 0.35rem;
border-radius: var(--radius-sm);
font-family: var(--font-mono);
font-size: 0.85rem;
}
</style>
More demos