> For the complete documentation index, see [llms.txt](https://docs.antivamp.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.antivamp.io/partners/05_signed_decisions.md).

# Signed Decisions

Decisions from `POST /v1/launches/validate` are **Ed25519-signed** and short-lived so a launchpad can trust them without trusting the transport, and so a stale `allow` can never be replayed.

## Payload fields

```
version           e.g. "av-decision-v2"
requestId         unique per validation
identityKey       NAME::TICKER (normalized)
normalizedName
normalizedTicker
chain
launchpadId
launcher
decision          allow | allow_authorized_only | block | recheck_required
reason
issuedAt          ISO-8601
expiresAt         ISO-8601 (issuedAt + 5 min)
nonce
signature         base64 Ed25519
signatureAlg      "ed25519"
keyId             which public key signed it
```

## Canonical serialization

The signed bytes are the fields joined by `\n` in this exact order, launcher lowercased:

```
version, identityKey, normalizedName, normalizedTicker, chain, launchpadId,
launcher(lowercased), decision, reason, issuedAt, expiresAt, nonce, requestId
```

## Verify (recommended: SDK)

```ts
const v = await antivamp.verifyValidationDecision(decision);
// { valid: boolean, reason: "ok"|"expired"|"not_yet_valid"|"unknown_key"|"bad_signature"|"no_signature" }
if (!v.valid) reject(v.reason);
```

## Verify (manual, Node)

```ts
import { createPublicKey, verify } from "node:crypto";
const { keys } = await (await fetch("https://antivamp.io/api/v1/keys")).json();
const key = keys.find(k => k.kid === d.keyId) ?? keys[0];
const spki = Buffer.concat([Buffer.from("302a300506032b6570032100","hex"),
  Buffer.from(key.publicKeyBase64,"base64")]);
const pub = createPublicKey({ key: spki, format: "der", type: "spki" });
const canonical = [d.version,d.identityKey,d.normalizedName,d.normalizedTicker,
  d.chain,d.launchpadId,d.launcher.toLowerCase(),d.decision,d.reason,
  d.issuedAt,d.expiresAt,d.nonce ?? "",d.requestId].join("\n");
const okSig = verify(null, Buffer.from(canonical), pub, Buffer.from(d.signature,"base64"));
const fresh = new Date(d.expiresAt) > new Date();
const trust = okSig && fresh;
```

## Key rotation

`GET /v1/keys` returns all currently-accepted public keys; exactly one is `active:true`. During rotation both old and new appear — accept any listed key, match by `keyId`. Cache keys briefly (the SDK caches 5 min).

## Clock skew & replay

Verification allows ±30s skew by default. Because each decision carries a unique `requestId`/`nonce` and a 5-minute `expiresAt`, an old captured `allow` cannot be replayed after expiry. Public verification test vectors ship with the SDK (`sdk/test/decision.vectors.json`).

## Convenience endpoint

`POST /v1/decisions/verify` (public) returns `{ valid, reason }` for a posted decision — handy for non-Node stacks or debugging. Prefer local verification in production.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.antivamp.io/partners/05_signed_decisions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
