AntiVampAntiVamp

Developers

Integrate identity validation in one afternoon.

Sandbox key in seconds, one validation call before each launch, signed decisions you can verify, and webhooks for protection changes.

Quickstart

Five minutes to a signed decision

curl
# 1. Create a sandbox key (instant, free — shown once)
curl -X POST https://antivamp.io/api/v1/sandbox/keys \
  -H "Content-Type: application/json" \
  -d '{"organization":"Your Launchpad","email":"dev@yourlaunchpad.xyz"}'

# 2. Validate a launch against a seeded scenario
curl -X POST https://antivamp.io/api/v1/launches/validate \
  -H "Authorization: Bearer av_sbx_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "hyperliquid",
    "launchpad": "your-launchpad",
    "name": "Sandbox Fox",
    "ticker": "SFOX",
    "launcher": "0x00000000000000000000000000000000000000a1"
  }'
# -> decision: "allow" (authorized wallet)
# Swap the launcher for any other address -> decision: "block"
TypeScript
const res = await fetch("https://antivamp.io/api/v1/launches/validate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ANTIVAMP_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": launchAttemptId,
  },
  body: JSON.stringify({
    chain: "hyperliquid",
    launchpad: "your-launchpad",
    name, ticker, launcher,
    nonce: launchAttemptId,
  }),
});

if (!res.ok) {
  // AntiVamp fails closed: treat errors as "do not launch".
  throw new Error(`validation unavailable (${res.status})`);
}
const decision = await res.json();
if (decision.decision !== "allow") rejectLaunch(decision.reason);
Python
import os, requests

r = requests.post(
    "https://antivamp.io/api/v1/launches/validate",
    headers={
        "Authorization": f"Bearer {os.environ['ANTIVAMP_API_KEY']}",
        "Idempotency-Key": launch_attempt_id,
    },
    json={
        "chain": "hyperliquid",
        "launchpad": "your-launchpad",
        "name": name, "ticker": ticker, "launcher": launcher,
        "nonce": launch_attempt_id,
    },
    timeout=8,
)
r.raise_for_status()  # fail closed on any error
decision = r.json()
assert decision["decision"] == "allow", decision["reason"]

Sandbox scenarios

Seeded identities for every state

Sandbox keys validate exclusively against these seeded scenarios — production data is never touched. Test authorized launches, blocked copycats, expiry, and guardian corrections.

Name :: TickerSeeded state
SANDBOXFOX :: SFOXActive reservation — launcher 0x…00a1 is authorized
MOONBADGER :: BADGERProtected after bonding (reserved, 60-day lock)
IRONWHALE :: IRONProtected after verified ~$1M milestone (90-day lock)
DUSKRAVEN :: DUSKExpired protection — available again
VAMPCOIN :: VAMPGraduated identity block — always blocked
CLEARSKY :: CLEARGuardian-cleared — available after mistaken block
NIGHTSHADE :: SHADEUnreserved bond — short 48-hour lock
anything elseAvailable

Signed responses

Verify every decision and webhook

Validation decisions are HMAC-SHA256 signed with your per-key signing secret and expire after 5 minutes, so stale allow decisions cannot be replayed. Webhooks carry a timestamped signature header.

Verify a decision (TypeScript)
import { createHmac, timingSafeEqual } from "node:crypto";

// Decision signature: HMAC-SHA256 over this exact canonical string,
// keyed with the signing secret issued alongside your API key.
function verifyDecision(secret: string, d: DecisionResponse): boolean {
  const payload = [
    "antivamp.decision.v1",
    d.identityKey,
    d.chain,             // the chain you sent in the request
    d.launcher.toLowerCase(),
    d.decision,
    d.issuedAt,
    d.expiresAt,
    d.nonce,
    d.responseVersion,
  ].join("\n");
  const expected = createHmac("sha256", secret).update(payload).digest("hex");
  const fresh = new Date(d.expiresAt).getTime() > Date.now();
  return fresh && timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(d.signature, "hex"),
  );
}
Verify a webhook (TypeScript)
// Header: X-AntiVamp-Signature: t=1789544400,v1=<hex>
// Signature: HMAC-SHA256(secret, `${t}.${rawBody}`)
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyWebhook(secret: string, rawBody: string, header: string) {
  const { t, v1 } = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  );
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // 5 min tolerance
  const mac = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(mac, "hex"), Buffer.from(v1, "hex"));
}

Normalization standard v1

One fold. Names and tickers. Every chain.

Both names and tickers pass through the same canonical fold, mirrored exactly by the on-chain SymbolLib, this site, the partner API, and the registry index. Version 1 rules:

  • Uppercase ASCII letters; lowercase folds up.
  • Leet digits fold to look-alike letters: 0→O 1→I 3→E 4→A 5→S 7→T 8→B. Other digits (2, 6, 9) are kept.
  • $→S and @→A.
  • Whitespace, punctuation, and emoji are dropped.
  • All non-ASCII is dropped — full-width unicode, zero-width characters, and homoglyph scripts cannot smuggle a look-alike identity past the fold. Inputs that fold to empty are rejected on-chain (BadLength).
  • Identity key = NAME::TICKER after folding; on-chain keys are keccak256 of each folded string.
  • Length limits are enforced on-chain per registry config; a future v2 fold would ship as a new registry version with documented migration, never a silent change.

Official test vectors (version 1) are published in the repository and exercised by the unit test suite.

InputCanonicalCategory
dogeDOGEcase
DoGeDOGEcase
Green Robin GREENROBINwhitespace
Green RobinGREENROBINwhitespace
$DOGESDOGEdollar_sign
@DOGEADOGEat_sign
D0GEDOGEleet
P3P3PEPEleet
5HIBSHIBleet
B0NK1BONKIleet
7RUMPTRUMPleet
8ONKBONKleet

Operational notes

Limits and versioning

Rate limits

Sandbox keys: 60 requests/minute (fixed window). 429 with rate_limited when exceeded. Production limits are set per partner.

Idempotency

Send Idempotency-Key (or a request nonce) on validation calls — retries replay the stored response with Idempotency-Replayed: true.

Versioning

Responses carry responseVersion. Breaking changes ship as a new version with a migration window — see the changelog.

Machine-readable spec: GET /api/v1/openapi · SDK packages (TypeScript, Python) are planned; the API is plain HTTP today.