Skip to content
Guide · Web3

Sybil-resistant airdrops & governance

Wallet-graph analytics catch the lazy farms; the sophisticated ones rotate fresh wallets behind VMs and proxies. Anchor eligibility to the device: a real TPM costs real money to mint while a fresh wallet costs nothing, so one human with one machine can't farm a thousand allocations.

The shape

  1. User connects their wallet to your claim/governance dApp. Before they sign the eligibility message, the dumb client collects an opaque evidence blob (over a nonce your server minted) and posts it to your backend, with no keys and no Root Herald contact from the client.
  2. Your backend calls rh.verify() server→server, gets a stable per-tenant device.ueid back directly, and binds (device.ueid → walletAddress).
  3. Enforce the rule that matches your distribution: one allocation per device, weight allocation by device-distinct wallets, or flag wallets that share a device.
claim handlerts
import { RootHerald } from "@rootherald/node";

const rh = new RootHerald({ secretKey: process.env.RH_SECRET_KEY! }); // rh_sk_…

// Earlier: GET /claim/challenge -> rh.issueChallenge() -> relay nonce to the dApp.
// The dApp quotes over the nonce and posts back { challengeId, evidence, wallet }.
const verdict = await rh.verify(req.body.evidence, {
  challengeId: req.body.challengeId,
  policy: "rootherald:builtin:strict-hardware",
});
if (verdict.device.verdict !== "pass") {
  return res.status(403).json({ error: "device_check_failed" });
}

// One allocation per device. (Or: store all (device.ueid, wallet) pairs and weight at snapshot.)
const existing = await db.allocations.findByDevice(verdict.device.ueid);
if (existing && existing.wallet !== wallet) {
  return res.status(409).json({ error: "device_already_claimed", with: existing.wallet });
}
await db.allocations.upsert({ deviceId: verdict.device.ueid, wallet, attestationType: verdict.device.attestationType });

Policy: reject the cloud

This is the use case that most wants the default policy (rootherald:builtin:strict-hardware). A farmer's cheapest move is a fleet of cloud VMs, and a cloud vTPM (AWS NitroTPM, Azure, GCP) is an identity they mint for pennies. The default rejects every cloud-vtpm and emulated class, so a NitroTPM-backed claim fails outright. Keep it. If you operate your own infrastructure and have a legitimate reason to allow it, you can, but it'll require cloud cross-validation evidence (see customize).

Heads up
Crypto-native users will install a thin attest client; the audience expects it. But state the privacy posture plainly: Root Herald sees only a hardware-derived pseudonym, never the wallet, never PII, and the pseudonym is uncorrelatable across tenants. That framing is what keeps you out of the political territory that sank Web Environment Integrity.

What about pre-snapshot farming?

Bind devices continuously, not just at the snapshot. Run the appraisal (collect evidence → rh.verify()) on connect and on any reward-relevant action; store the device set over time. A wallet that only ever appears alongside the same device as 200 other wallets is the signal you want, and it's visible even when each wallet's on-chain history looks clean.