Developers
Ship identity protection in one afternoon.
Sandbox key in seconds. One enforceLaunch call before mint. Ed25519-signed decisions. Webhooks when protection changes. Fail closed — always.
Minutes, not weeks
Self-serve sandbox key → signed allow/block before lunch.
Ed25519 decisions
Public-key verify. 5-minute TTL. No shared decision secrets.
Fail closed
Outages and allow_authorized_only never silently allow.
Quickstart
Five minutes to a signed decision
# 1. Create a sandbox key (instant, free — shown once)
curl -X POST https://sandbox-api.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://sandbox-api.antivamp.io/api/v1/launches/validate \
-H "Authorization: Bearer av_sbx_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain": "solana",
"launchpad": "your-launchpad",
"name": "Sandbox Fox",
"ticker": "SFOX",
"launcher": "0x00000000000000000000000000000000000000a1"
}'
# -> decision: "allow" (authorized wallet)
# Swap the launcher for any other address -> decision: "block"import { AntiVampClient, AntiVampFailClosedError } from "@antivamp_io/sdk";
const antivamp = new AntiVampClient({
apiKey: process.env.ANTIVAMP_API_KEY,
baseUrl: "https://sandbox-api.antivamp.io", // prod: https://api.antivamp.io
});
try {
// enforceLaunch verifies Ed25519 + maps decisions exhaustively
const result = await antivamp.enforceLaunch({
chain: "solana",
launchpad: "your-launchpad",
name, ticker, launcher,
});
if (!result.enforceable) rejectLaunch(result.userMessage);
else proceed(result.raw);
} catch (e) {
if (e instanceof AntiVampFailClosedError) rejectLaunch("validation unavailable");
else throw e;
}import os, requests
r = requests.post(
"https://sandbox-api.antivamp.io/api/v1/launches/validate",
headers={
"Authorization": f"Bearer {os.environ['ANTIVAMP_API_KEY']}",
"Idempotency-Key": launch_attempt_id,
},
json={
"chain": "solana",
"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.get("signature"), "unsigned decisions must be rejected"
assert decision["decision"] == "allow", decision["reason"]# Full identity lifecycle in the sandbox
KEY="av_sbx_YOUR_KEY"; API="https://sandbox-api.antivamp.io/api/v1"
AUTHW="0xAAA0000000000000000000000000000000000001"
# 1. Reserve a pair for 7 days (sandbox reservations are API-created;
# production reservations happen on-chain)
curl -X POST $API/reservations -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"chain":"base","name":"Robin Lifecycle","ticker":"RLC","launcher":"'$AUTHW'","durationSeconds":604800}'
# 2. Authorized wallet -> "authorized" / "allow"
curl -X POST $API/launches/validate -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"chain":"base","launchpad":"you","name":"Robin Lifecycle","ticker":"RLC","launcher":"'$AUTHW'"}'
# 3. Copycat wallet -> "blocked" / "block" — even leet look-alikes
# ("R0bin Lifecycle" / "rlc") fold to the same identity key
curl -X POST $API/launches/validate -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"chain":"base","launchpad":"you","name":"R0bin Lifecycle","ticker":"rlc","launcher":"0xBBB0000000000000000000000000000000000002"}'
# 4. Report the launch
curl -X POST $API/launches/report -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"chain":"base","launchpad":"you","name":"Robin Lifecycle","ticker":"RLC","launcher":"'$AUTHW'","tokenOrMint":"0xYourToken"}'
# 5. Report bonding -> 60-day reserved lock (never shortens existing locks)
curl -X POST $API/events/bonded -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"chain":"base","launchpad":"you","name":"Robin Lifecycle","ticker":"RLC","wasReserved":true}'
# 6. Watch the identity update everywhere — public, no key needed
curl "$API/identity/status?key=ROBINLIFECYCLE::RLC"In-app reserve
Let creators reserve without leaving your launchpad
Two integration paths — both settle on-chain and keep your av_live_ key server-side.
Hosted Reserve widget
Drop-in iframe with your logo + accent color. Wallet connect, quote, sign, and confirmation — postMessage events back to your app.
antivamp.io/embed/reserve.js
Prepare API
Your backend calls prepare → creator signs the returned transaction → poll status. Full UX control, scope reservation:prepare.
POST /api/v1/reservations/prepare
Full guide: Launchpad Embed docs
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 Ed25519-signed (asymmetric — verify with GET /v1/keys) and expire after 5 minutes so stale allows cannot be replayed. Webhooks use a separate per-endpoint HMAC secret.
import { AntiVampClient } from "@antivamp_io/sdk";
const antivamp = new AntiVampClient({
apiKey: process.env.ANTIVAMP_API_KEY,
baseUrl: "https://api.antivamp.io",
});
// Preferred: one call that verifies signature + enforces exhaustively
const result = await antivamp.enforceLaunch({
chain, launchpad, name, ticker, launcher,
});
if (!result.enforceable) rejectLaunch(result.userMessage);
// Or verify manually (exact keyId — never fall back to keys[0])
const decision = await antivamp.validateLaunch({ chain, launchpad, name, ticker, launcher });
const v = await antivamp.verifyValidationDecision(decision);
if (!v.valid) rejectLaunch(v.reason);// 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"));
}Public verification keys: GET /api/v1/keys · install the SDK: npm install @antivamp_io/sdk · run conformance: npx antivamp-conformance
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 2) are published in the repository and exercised by the unit test suite.
| Input | Canonical | Category |
|---|---|---|
| Green Robin | GREENROBIN | frozen_robin |
| GREEN ROBIN | GREENROBIN | frozen_robin |
| Green Robin | GREENROBIN | frozen_robin |
| $ROBIN | SROBIN | frozen_robin |
| robin | ROBIN | frozen_robin |
| ROBIN | ROBIN | frozen_robin |
| doge | DOGE | case |
| DoGe | DOGE | case |
| Green Robin | GREENROBIN | whitespace |
| Green Robin | GREENROBIN | whitespace |
| $DOGE | SDOGE | dollar_sign |
| @DOGE | ADOGE | at_sign |
Operational notes
Limits and versioning
Rate limits
Sandbox keys: 60 requests/minute. Production keys: 300/minute by default, raised per partner up to 2000/minute on request. Fixed windows aligned to the clock minute. Retry-After on every rate-limit 429, and RateLimit-Remaining on successful /launches/validate and /identity/check responses, so you can pace yourself rather than find the ceiling by hitting it.
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 · TypeScript SDK: npm install @antivamp_io/sdk · Hosts: api.antivamp.io / sandbox-api.antivamp.io
Integration help: support@antivamp.io · Partnership and production access: partners@antivamp.io