# Verified identity
URL: /docs/x1id/verification

> Read a handle's blue-tick — whether it has proved control of an X, Discord, GitHub, Telegram account or a domain — from the resolve result or straight from chain, with the staleness rule enforced.

# Verified identity

Beyond the per-chain `verification` a `resolve()` returns, a handle can carry **per-platform verifications** — the blue-tick signal that a handle proved control of an X, Discord, GitHub, or Telegram account, or a domain. This page is how you read that signal.

<LlmActions />

<Callout type="info">
  This page targets **`@x1id/resolve@0.21.0`**. The registry is on **X1 mainnet** at `8JgnNWi24bq9uzfnT9XmkWxvaWMVgoEs9bu8QsHhLe1P` (`https://rpc.mainnet.x1.xyz`).
</Callout>

## Two ways to read it

A verification can reach you two ways, and the helpers tolerate either:

1. **From a `Resolved`'s `verifications[]`** — when the producing resolver supplies it (the tools/api REST `/resolve` response carries this field). The SDK's own on-chain `resolve()` does **not** populate it.
2. **Straight from chain** — read the `Attestation` accounts yourself with `fetchAttestations`. This applies the staleness rule structurally and needs no REST layer.

## From a resolve result

If your `Resolved` carries `verifications[]`, three absence-tolerant helpers read it:

```ts
import { isVerified, verifiedPlatforms, isVerifiedOn } from '@x1id/resolve';

// `resolved` is a Resolved carrying the resolver's verifications[], e.g.
//   verifications: [
//     { platform: 'x',      value: '@michelle',    verified: true, attested_at: 1730000000 },
//     { platform: 'github', value: 'michelle',     verified: true, attested_at: 1730000100 },
//     { platform: 'domain', value: 'michelle.com', verified: true, attested_at: 1730000200 },
//   ]

isVerified(resolved);              // true  — show the blue tick (any live signal)
verifiedPlatforms(resolved);       // ['x', 'github', 'domain']
isVerifiedOn(resolved, 'github');  // true
isVerifiedOn(resolved, 'telegram'); // false
```

All three are **absence-tolerant**: an older resolver, the SDK's own on-chain `resolve()`, or a handle with no live verifications all yield `false` / `[]`, never a throw. Never index into `verifications` blindly — go through these helpers.

**Rendering a badge.** Light the tick when `isVerified(resolved)` is true; on hover/focus, list `verifiedPlatforms(resolved)` and pull each platform's account from the paired `value` (`@michelle` for `x`, `michelle.com` for `domain`). Platform slugs are `x` / `discord` / `github` / `telegram`, plus legacy `domain` (DNS) and `social` (pre-per-platform, no attribution).

## Straight from chain

To compute the same signal without a REST layer, read the `Attestation` accounts directly. Every function demands the handle's `registeredAt` and `recordsClearedAt` so the staleness rule is applied structurally — there is no way to fetch an attestation without them.

```ts
import {
  fetchAttestations, liveAttestations, isHandleVerified,
  platformSlug, textRecordKeyFor,
} from '@x1id/resolve';

// handleAccount / registeredAt / recordsClearedAt come from the Handle you
// resolved (name → deriveHandleAccount → decode the Handle account).
const atts = await fetchAttestations(rpc, programId, handleAccount, registeredAt, recordsClearedAt);

isHandleVerified(atts); // the blue-tick condition, from chain

for (const a of liveAttestations(atts)) {
  platformSlug(a.kind);     // 'x' | 'discord' | 'github' | 'telegram' | null (legacy)
  textRecordKeyFor(a.kind); // 'com.x' | ... | 'website' | null — where the paired value lives
}
```

* **`fetchAttestations`** returns *every* attestation of a handle, stale ones flagged, so an owner surface can show what a previous registration left behind. Anything that renders a badge should take `isHandleVerified` / `liveAttestations`.
* **`isHandleVerified`** implements the rule below: true when at least one non-stale attestation of any kind exists.
* **`platformSlug` / `textRecordKeyFor`** mirror the on-chain `Attestation::platform_slug` and the shared contract table exactly, so joining a live attestation to its paired text record uses the same mapping the program, resolver and app badge use.

## Attestation kinds

| Kind | Constant                    | Slug              | Paired text record                  |
| ---- | --------------------------- | ----------------- | ----------------------------------- |
| `0`  | `ATTESTATION_KIND_DNS`      | `dns` (legacy)    | `website`                           |
| `1`  | `ATTESTATION_KIND_SOCIAL`   | `social` (legacy) | —                                   |
| `2`  | `ATTESTATION_KIND_X`        | `x`               | `com.x`                             |
| `3`  | `ATTESTATION_KIND_DISCORD`  | `discord`         | `com.discord`                       |
| `4`  | `ATTESTATION_KIND_GITHUB`   | `github`          | `com.github`                        |
| `5`  | `ATTESTATION_KIND_TELEGRAM` | `telegram`        | `org.telegram`                      |
| `6`  | `KIND_VALIDATOR`            | `validator`       | — (vote account in `evidence_hash`) |

The two legacy kinds (`dns`, `social`) are kept valid forever so existing accounts are never orphaned; `platformSlug` returns `null` for them because neither names a single modern platform. A `HandleAttestation` also exposes `account`, `handle`, `kind`, `kindName`, `evidenceHash`, `attestedAt`, `attestor`, and `stale`.

<Callout type="info">
  **Kind `6` is the validator shield, not a blue-tick platform.** Unlike kinds `0`–`5`, it is minted trustlessly on-chain (no attestor) and surfaces as a distinct **gold shield**, so render it separately rather than as a social-verification row. Its `evidence_hash` holds the validator's vote account. See [Verified validators](/docs/x1id/validators) for both instructions, the challenge format, and the Verify-your-validator flow.
</Callout>

### Verified = one live signal of any kind

A handle is verified when **at least one non-stale attestation of any kind** exists — a single strong signal is enough (requiring two would reject Nike proving control of nike.com). `kind` records which signal proved it.

## The staleness rule

<Callout type="warn">
  An attestation only vouches for the **current** owner when it is live. A stale attestation belongs to a previous, unrelated owner of the same name and must never be rendered as verifying the current one.
</Callout>

A `Handle`'s address is `["handle", name]` — a pure function of the name — so release + re-register lands the new registration at the *same* pubkey, and the previous owner's attestation is physically attached to the new owner's name with no action by anyone. Attestations do not expire on a timer; they are invalidated by **ownership epoch**. An attestation is `stale` (and so excluded by `liveAttestations` / `isHandleVerified`) when **either**:

* `attested_at < handle.registered_at` — predates the current registration; **or**
* `attested_at < handle.records_cleared_at` — the owner ran `clear_records`, bulk-invalidating records and attestations without a transfer.

One documented blind spot, shared with `Handle.owner` and `Primary`: a bearer-NFT marketplace trade bumps no epoch, so an attestation keeps reading live until the attestor re-reviews or revokes. Re-check verification per session for anything high-stakes.

## What a verification proves — honestly

A verification is an **off-chain review** (a DNS lookup, an OAuth completion, a human check) that X1ID's **attestor key** signed off on at `attested_at`. It is **not** a trustless proof: that key is admin-rotatable, so the trust anchor is "whoever the registry admin currently designates." Render it as **"verified by x1id"**, never as more. (The trustless upgrade for the domain signal is DNSSEC/zkTLS, on the roadmap.)

## Next

<Cards>
  <Card href="/docs/x1id/gating" icon="ShieldCheck" title="Attestation gating" description="Turn these same signals into a verified-only allowlist for your app or mint." />

  <Card href="/docs/x1id/records" icon="Link" title="Address records" description="The per-chain payment records that the paired text records sit alongside." />

  <Card href="/docs/x1id/sdk" icon="Code" title="SDK reference" description="resolve, reverse, records, and the full type surface." />
</Cards>
