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
- User connects their wallet to your claim/governance dApp. Before they sign the eligibility message, the dumb client collects an opaque
evidenceblob (over a nonce your server minted) and posts it to your backend, with no keys and no Root Herald contact from the client. - Your backend calls
rh.verify()server→server, gets a stable per-tenantdevice.ueidback directly, and binds(device.ueid → walletAddress). - Enforce the rule that matches your distribution: one allocation per device, weight allocation by device-distinct wallets, or flag wallets that share a device.
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).
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.