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
# 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"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);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 :: Ticker | Seeded state |
|---|---|
| SANDBOXFOX :: SFOX | Active reservation — launcher 0x…00a1 is authorized |
| MOONBADGER :: BADGER | Protected after bonding (reserved, 60-day lock) |
| IRONWHALE :: IRON | Protected after verified ~$1M milestone (90-day lock) |
| DUSKRAVEN :: DUSK | Expired protection — available again |
| VAMPCOIN :: VAMP | Graduated identity block — always blocked |
| CLEARSKY :: CLEAR | Guardian-cleared — available after mistaken block |
| NIGHTSHADE :: SHADE | Unreserved bond — short 48-hour lock |
| anything else | Available |
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.
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"),
);
}// 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. $→Sand@→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::TICKERafter folding; on-chain keys arekeccak256of 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.
| Input | Canonical | Category |
|---|---|---|
| doge | DOGE | case |
| DoGe | DOGE | case |
| Green Robin | GREENROBIN | whitespace |
| Green Robin | GREENROBIN | whitespace |
| $DOGE | SDOGE | dollar_sign |
| @DOGE | ADOGE | at_sign |
| D0GE | DOGE | leet |
| P3P3 | PEPE | leet |
| 5HIB | SHIB | leet |
| B0NK1 | BONKI | leet |
| 7RUMP | TRUMP | leet |
| 8ONK | BONK | leet |
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.