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.
This page targets @x1id/[email protected]. The registry is on X1 mainnet at 8JgnNWi24bq9uzfnT9XmkWxvaWMVgoEs9bu8QsHhLe1P (https://rpc.mainnet.x1.xyz).
Two ways to read it
A verification can reach you two ways, and the helpers tolerate either:
- From a
Resolved'sverifications[]— when the producing resolver supplies it (the tools/api REST/resolveresponse carries this field). The SDK's own on-chainresolve()does not populate it. - Straight from chain — read the
Attestationaccounts yourself withfetchAttestations. 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:
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'); // falseAll 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.
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
}fetchAttestationsreturns 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 takeisHandleVerified/liveAttestations.isHandleVerifiedimplements the rule below: true when at least one non-stale attestation of any kind exists.platformSlug/textRecordKeyFormirror the on-chainAttestation::platform_slugand 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.
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 for both instructions, the challenge format, and the Verify-your-validator flow.
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
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.
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; orattested_at < handle.records_cleared_at— the owner ranclear_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.)
