@x1id/resolve 0.2.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Agent records — identify, describe, and verify an `@handle` operating as
3
+ * an AI agent. See `docs/agent-records.md` for the full design and the
4
+ * honest account of what each verification level does and does not prove.
5
+ *
6
+ * This is conventions on EXISTING primitives, not a new account family:
7
+ * `HandleType.Agent` (already on-chain), well-known `TextRecord` keys
8
+ * (`textRecords.ts`, #7376), address `Record`s (`records.ts`), and
9
+ * `Attestation` (`attestation.ts`, #7379). Nothing here requires a program
10
+ * change.
11
+ *
12
+ * Hand-rolled like the rest of this package — no schema-interpreter
13
+ * dependency, no `@solana/web3.js` import. `validateManifest` is plain
14
+ * field checks, matching how `records.ts`/`textRecords.ts`/`attestation.ts`
15
+ * hand-decode rather than lean on a generic library.
16
+ *
17
+ * # L2 ("pay-to confirmed") is deliberately NOT implemented here
18
+ *
19
+ * It's a live HTTP fact against an arbitrary third-party host — forcing a
20
+ * network call inside a library used in contexts that may not want one.
21
+ * That check (`probe402`) belongs in a service that's already making
22
+ * network calls on the caller's behalf — tracked as the x1id MCP server,
23
+ * WP #8133. `AgentVerificationLevel` still names level 2 so every surface
24
+ * that reports a level shares one vocabulary.
25
+ */
26
+ import type { HandleTextRecord } from "./textRecords.js";
27
+ import type { HandleAttestation } from "./attestation.js";
28
+ import type { HandleRecord } from "./records.js";
29
+ /** The six well-known `TextRecord` keys an agent handle may set. Every
30
+ * value must be `https://` EXCEPT `manifest`, which may also be
31
+ * `ipfs://<cid>` — see {@link checkAgentUrl}. */
32
+ export declare const AGENT_RECORD_KEYS: Readonly<{
33
+ readonly manifest: "agent.manifest";
34
+ readonly x402: "agent.x402";
35
+ readonly mcp: "agent.mcp";
36
+ readonly a2a: "agent.a2a";
37
+ readonly attestation: "agent.attestation";
38
+ readonly capabilities: "agent.capabilities";
39
+ }>;
40
+ export type AgentEndpointType = "x402" | "mcp" | "a2a" | "http";
41
+ /** `agent.manifest` v1 (`docs/agent-records.md` §3), as accepted by
42
+ * {@link validateManifest}. */
43
+ export interface AgentManifest {
44
+ readonly x1id: {
45
+ readonly version: 1;
46
+ readonly name: string;
47
+ readonly network: "testnet" | "mainnet";
48
+ /** Base58 address of the `Handle` PDA — the uniqueness anchor. */
49
+ readonly handle: string;
50
+ };
51
+ readonly identity?: {
52
+ /** Base58 address of a non-stale `Attestation` account on this handle
53
+ * (kind DNS or SOCIAL). See `docs/agent-records.md` §4 for what this
54
+ * does and does not prove — it is NOT an agent-identity-registry
55
+ * ownership check, that concept has no SVM equivalent yet. */
56
+ readonly attestation?: string;
57
+ readonly description?: string;
58
+ };
59
+ readonly endpoints?: readonly {
60
+ readonly type: AgentEndpointType;
61
+ readonly url: string;
62
+ }[];
63
+ readonly capabilities?: {
64
+ readonly schema?: string;
65
+ readonly url: string;
66
+ };
67
+ readonly payment?: {
68
+ readonly addresses?: readonly {
69
+ readonly coinType: number;
70
+ readonly address: string;
71
+ readonly verified?: boolean;
72
+ }[];
73
+ readonly x402?: {
74
+ readonly network: string;
75
+ readonly asset?: string;
76
+ readonly scheme?: string;
77
+ readonly facilitator?: string;
78
+ readonly extra?: Readonly<Record<string, unknown>>;
79
+ };
80
+ };
81
+ }
82
+ /** URL rule (`docs/agent-records.md` §2): every URL field is `https://`
83
+ * ONLY, except `agent.manifest`'s own record value, which may also be
84
+ * `ipfs://<cid>` (`allowIpfs`). */
85
+ export declare function checkAgentUrl(url: string, allowIpfs?: boolean): boolean;
86
+ export interface ValidateManifestOptions {
87
+ /** The handle name this manifest is being read FOR (no `@`). Required —
88
+ * a manifest that doesn't match is discarded, never partially trusted. */
89
+ readonly expectedName: string;
90
+ /** The `Handle` PDA address (base58) this manifest is being read FOR. */
91
+ readonly expectedHandlePda: string;
92
+ readonly expectedNetwork: "testnet" | "mainnet";
93
+ /** The handle's live, non-stale address records — when supplied, every
94
+ * `payment.addresses[]` entry claiming `verified: true` is cross-checked
95
+ * against them and rejected if the chain disagrees ("the chain wins",
96
+ * `docs/agent-records.md` §3). Omit to skip this cross-check (shape-only
97
+ * validation). */
98
+ readonly addressRecords?: readonly HandleRecord[];
99
+ }
100
+ export type ManifestValidationError = {
101
+ readonly code: "not-an-object";
102
+ } | {
103
+ readonly code: "missing-field";
104
+ readonly field: string;
105
+ } | {
106
+ readonly code: "bad-version";
107
+ } | {
108
+ readonly code: "name-mismatch";
109
+ } | {
110
+ readonly code: "handle-mismatch";
111
+ } | {
112
+ readonly code: "network-mismatch";
113
+ } | {
114
+ readonly code: "bad-url";
115
+ readonly field: string;
116
+ } | {
117
+ readonly code: "bad-attestation-address";
118
+ } | {
119
+ readonly code: "bad-payment-address";
120
+ } | {
121
+ readonly code: "overclaims-verified";
122
+ readonly coinType: number;
123
+ };
124
+ export type ManifestValidationResult = {
125
+ readonly ok: true;
126
+ readonly manifest: AgentManifest;
127
+ } | {
128
+ readonly ok: false;
129
+ readonly errors: readonly ManifestValidationError[];
130
+ };
131
+ /**
132
+ * Validate a manifest's shape AND, when `addressRecords` is supplied, its
133
+ * agreement with the chain. Never trusts a claim the manifest makes about
134
+ * itself over what the caller already knows to be true on-chain.
135
+ */
136
+ export declare function validateManifest(raw: unknown, opts: ValidateManifestOptions): ManifestValidationResult;
137
+ /** Pick the agent-related well-known keys out of a handle's live text
138
+ * records (pass the output of `liveTextRecords` — stale entries excluded
139
+ * already, matching every other read path's convention). */
140
+ export declare function agentTextRecords(records: readonly HandleTextRecord[]): Partial<Record<keyof typeof AGENT_RECORD_KEYS, string>>;
141
+ /** L2 is intentionally never returned here — see the module docs. `null`
142
+ * means "not even declared." */
143
+ export type AgentVerificationLevel = 0 | 1 | null;
144
+ export interface AgentVerificationInput {
145
+ readonly handleType: number;
146
+ /** `agentTextRecords(...)`'s output for this handle. */
147
+ readonly agentRecords: Partial<Record<keyof typeof AGENT_RECORD_KEYS, string>>;
148
+ /** This handle's live (non-stale) attestations — `liveAttestations(...)`. */
149
+ readonly liveAttestations: readonly HandleAttestation[];
150
+ /** The parsed manifest, if `agent.manifest` was fetched and validated.
151
+ * Omit if the manifest wasn't fetched — L1 is then reported as
152
+ * "not checked" (null), never "failed", per the reporting rule below. */
153
+ readonly manifest?: AgentManifest;
154
+ }
155
+ /**
156
+ * L0/L1, computed from data the caller already has (no RPC calls in this
157
+ * function — see the module docs for why L2 requires a live network call
158
+ * and lives elsewhere).
159
+ *
160
+ * A reader reports only the highest level it actually checked. If
161
+ * `manifest` wasn't supplied, L1 is reported `null` ("not checked"), not
162
+ * `0` ("failed") — the same discipline ArcNS's reference design uses.
163
+ */
164
+ export declare function agentVerificationLevel(input: AgentVerificationInput): AgentVerificationLevel;
165
+ export interface X402AcceptHint {
166
+ readonly scheme: string;
167
+ readonly network: string;
168
+ readonly resource: string;
169
+ readonly asset?: string;
170
+ readonly payTo?: string;
171
+ readonly facilitator?: string;
172
+ readonly extra?: Readonly<Record<string, unknown>>;
173
+ }
174
+ /**
175
+ * Turn a validated manifest into x402-shaped `accepts[]` hints. NEVER
176
+ * authoritative — the resource's own live 402 response always wins; this is
177
+ * a pre-flight convenience so a caller doesn't have to probe every endpoint
178
+ * just to learn the shape it should expect.
179
+ *
180
+ * `opts.payTo`, when supplied, is preferred over anything the manifest
181
+ * claims for itself (chain-verified address beats a self-reported mirror).
182
+ * Returns `[]` for an agent with no `x402` endpoints.
183
+ */
184
+ export declare function x402AcceptsFromManifest(manifest: AgentManifest, opts?: {
185
+ readonly payTo?: string;
186
+ }): readonly X402AcceptHint[];
package/dist/agent.js ADDED
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Agent records — identify, describe, and verify an `@handle` operating as
3
+ * an AI agent. See `docs/agent-records.md` for the full design and the
4
+ * honest account of what each verification level does and does not prove.
5
+ *
6
+ * This is conventions on EXISTING primitives, not a new account family:
7
+ * `HandleType.Agent` (already on-chain), well-known `TextRecord` keys
8
+ * (`textRecords.ts`, #7376), address `Record`s (`records.ts`), and
9
+ * `Attestation` (`attestation.ts`, #7379). Nothing here requires a program
10
+ * change.
11
+ *
12
+ * Hand-rolled like the rest of this package — no schema-interpreter
13
+ * dependency, no `@solana/web3.js` import. `validateManifest` is plain
14
+ * field checks, matching how `records.ts`/`textRecords.ts`/`attestation.ts`
15
+ * hand-decode rather than lean on a generic library.
16
+ *
17
+ * # L2 ("pay-to confirmed") is deliberately NOT implemented here
18
+ *
19
+ * It's a live HTTP fact against an arbitrary third-party host — forcing a
20
+ * network call inside a library used in contexts that may not want one.
21
+ * That check (`probe402`) belongs in a service that's already making
22
+ * network calls on the caller's behalf — tracked as the x1id MCP server,
23
+ * WP #8133. `AgentVerificationLevel` still names level 2 so every surface
24
+ * that reports a level shares one vocabulary.
25
+ */
26
+ import { X1_TESTNET_NETWORK } from "./x402.js";
27
+ // ---------------------------------------------------------------------------
28
+ // Well-known text-record keys
29
+ // ---------------------------------------------------------------------------
30
+ /** The six well-known `TextRecord` keys an agent handle may set. Every
31
+ * value must be `https://` EXCEPT `manifest`, which may also be
32
+ * `ipfs://<cid>` — see {@link checkAgentUrl}. */
33
+ export const AGENT_RECORD_KEYS = Object.freeze({
34
+ manifest: "agent.manifest",
35
+ x402: "agent.x402",
36
+ mcp: "agent.mcp",
37
+ a2a: "agent.a2a",
38
+ attestation: "agent.attestation",
39
+ capabilities: "agent.capabilities",
40
+ });
41
+ const HTTPS_RE = /^https:\/\/[^\s@/]+(\/[^\s]*)?$/;
42
+ const IPFS_RE = /^ipfs:\/\/[^\s]+$/;
43
+ const BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
44
+ /** URL rule (`docs/agent-records.md` §2): every URL field is `https://`
45
+ * ONLY, except `agent.manifest`'s own record value, which may also be
46
+ * `ipfs://<cid>` (`allowIpfs`). */
47
+ export function checkAgentUrl(url, allowIpfs = false) {
48
+ if (HTTPS_RE.test(url) && url.length <= 2048)
49
+ return true;
50
+ return allowIpfs && IPFS_RE.test(url);
51
+ }
52
+ /**
53
+ * Validate a manifest's shape AND, when `addressRecords` is supplied, its
54
+ * agreement with the chain. Never trusts a claim the manifest makes about
55
+ * itself over what the caller already knows to be true on-chain.
56
+ */
57
+ export function validateManifest(raw, opts) {
58
+ const errors = [];
59
+ if (typeof raw !== "object" || raw === null) {
60
+ return { ok: false, errors: [{ code: "not-an-object" }] };
61
+ }
62
+ const m = raw;
63
+ const x1id = m.x1id;
64
+ if (typeof x1id !== "object" || x1id === null) {
65
+ return { ok: false, errors: [{ code: "missing-field", field: "x1id" }] };
66
+ }
67
+ if (x1id.version !== 1)
68
+ errors.push({ code: "bad-version" });
69
+ if (x1id.name !== opts.expectedName)
70
+ errors.push({ code: "name-mismatch" });
71
+ if (x1id.handle !== opts.expectedHandlePda)
72
+ errors.push({ code: "handle-mismatch" });
73
+ if (x1id.network !== opts.expectedNetwork)
74
+ errors.push({ code: "network-mismatch" });
75
+ const identity = m.identity;
76
+ if (identity !== undefined) {
77
+ const att = identity.attestation;
78
+ if (att !== undefined && (typeof att !== "string" || !BASE58_RE.test(att))) {
79
+ errors.push({ code: "bad-attestation-address" });
80
+ }
81
+ }
82
+ const endpoints = m.endpoints;
83
+ if (endpoints !== undefined) {
84
+ for (const e of endpoints) {
85
+ if (typeof e.url !== "string" || !checkAgentUrl(e.url, false)) {
86
+ errors.push({ code: "bad-url", field: "endpoints[].url" });
87
+ }
88
+ }
89
+ }
90
+ const capabilities = m.capabilities;
91
+ if (capabilities !== undefined) {
92
+ if (typeof capabilities.url !== "string" || !checkAgentUrl(capabilities.url, false)) {
93
+ errors.push({ code: "bad-url", field: "capabilities.url" });
94
+ }
95
+ if (capabilities.schema !== undefined &&
96
+ (typeof capabilities.schema !== "string" || !checkAgentUrl(capabilities.schema, false))) {
97
+ errors.push({ code: "bad-url", field: "capabilities.schema" });
98
+ }
99
+ }
100
+ const payment = m.payment;
101
+ if (payment !== undefined) {
102
+ const addresses = payment.addresses;
103
+ if (addresses !== undefined) {
104
+ for (const a of addresses) {
105
+ if (typeof a.address !== "string" || a.address.length === 0 || a.address.length > 128) {
106
+ errors.push({ code: "bad-payment-address" });
107
+ continue;
108
+ }
109
+ if (a.verified === true && opts.addressRecords) {
110
+ // "The chain wins": a manifest claiming verified:true for a coin
111
+ // type the chain doesn't actually verify (or that resolves to a
112
+ // different address) is rejected outright, never trusted.
113
+ const onChain = opts.addressRecords.find((r) => r.coinType === a.coinType && !r.stale);
114
+ const chainVerified = onChain !== undefined && onChain.address === a.address && onChain.verified === true;
115
+ if (!chainVerified)
116
+ errors.push({ code: "overclaims-verified", coinType: a.coinType });
117
+ }
118
+ }
119
+ }
120
+ const x402 = payment.x402;
121
+ if (x402 !== undefined) {
122
+ if (typeof x402.network !== "string" || x402.network.length === 0) {
123
+ errors.push({ code: "missing-field", field: "payment.x402.network" });
124
+ }
125
+ if (x402.facilitator !== undefined &&
126
+ (typeof x402.facilitator !== "string" || !checkAgentUrl(x402.facilitator, false))) {
127
+ errors.push({ code: "bad-url", field: "payment.x402.facilitator" });
128
+ }
129
+ }
130
+ }
131
+ if (errors.length > 0)
132
+ return { ok: false, errors };
133
+ return { ok: true, manifest: raw };
134
+ }
135
+ // ---------------------------------------------------------------------------
136
+ // Reading agent text records off a handle's already-fetched records
137
+ // ---------------------------------------------------------------------------
138
+ /** Pick the agent-related well-known keys out of a handle's live text
139
+ * records (pass the output of `liveTextRecords` — stale entries excluded
140
+ * already, matching every other read path's convention). */
141
+ export function agentTextRecords(records) {
142
+ const byKey = new Map(records.map((r) => [r.key, r.text]));
143
+ const out = {};
144
+ for (const [name, key] of Object.entries(AGENT_RECORD_KEYS)) {
145
+ const v = byKey.get(key);
146
+ if (v)
147
+ out[name] = v;
148
+ }
149
+ return out;
150
+ }
151
+ /** `HandleType.Agent`'s numeric value (matches `voucher.ts`'s
152
+ * `HandleType.Agent`). Duplicated here (not imported) to keep this module
153
+ * independent of `voucher.ts`'s import surface. */
154
+ const HANDLE_TYPE_AGENT = 3;
155
+ /**
156
+ * L0/L1, computed from data the caller already has (no RPC calls in this
157
+ * function — see the module docs for why L2 requires a live network call
158
+ * and lives elsewhere).
159
+ *
160
+ * A reader reports only the highest level it actually checked. If
161
+ * `manifest` wasn't supplied, L1 is reported `null` ("not checked"), not
162
+ * `0` ("failed") — the same discipline ArcNS's reference design uses.
163
+ */
164
+ export function agentVerificationLevel(input) {
165
+ const declared = input.handleType === HANDLE_TYPE_AGENT || input.agentRecords.manifest !== undefined;
166
+ if (!declared)
167
+ return null;
168
+ if (input.manifest === undefined)
169
+ return 0;
170
+ const claimedAttestation = input.manifest.identity?.attestation;
171
+ if (claimedAttestation === undefined)
172
+ return 0;
173
+ const matches = input.liveAttestations.some((a) => a.account === claimedAttestation);
174
+ return matches ? 1 : 0;
175
+ }
176
+ /**
177
+ * Turn a validated manifest into x402-shaped `accepts[]` hints. NEVER
178
+ * authoritative — the resource's own live 402 response always wins; this is
179
+ * a pre-flight convenience so a caller doesn't have to probe every endpoint
180
+ * just to learn the shape it should expect.
181
+ *
182
+ * `opts.payTo`, when supplied, is preferred over anything the manifest
183
+ * claims for itself (chain-verified address beats a self-reported mirror).
184
+ * Returns `[]` for an agent with no `x402` endpoints.
185
+ */
186
+ export function x402AcceptsFromManifest(manifest, opts) {
187
+ const x402Endpoints = (manifest.endpoints ?? []).filter((e) => e.type === "x402");
188
+ if (x402Endpoints.length === 0)
189
+ return [];
190
+ const x402 = manifest.payment?.x402;
191
+ // X1_TESTNET_NETWORK is the real, verified x402 network id (see x402.ts).
192
+ // Mainnet has no such id yet — X1 mainnet doesn't exist — so a manifest
193
+ // claiming network: "mainnet" without its own explicit x402.network gets
194
+ // an honestly-provisional placeholder rather than a fabricated genesis hash.
195
+ const network = x402?.network ?? (manifest.x1id.network === "testnet" ? X1_TESTNET_NETWORK : "x1:mainnet-unassigned");
196
+ const payTo = opts?.payTo ?? manifest.payment?.addresses?.[0]?.address;
197
+ return x402Endpoints.map((e) => {
198
+ const hint = {
199
+ scheme: x402?.scheme ?? "exact",
200
+ network,
201
+ resource: e.url,
202
+ };
203
+ if (x402?.asset !== undefined)
204
+ hint.asset = x402.asset;
205
+ if (payTo !== undefined)
206
+ hint.payTo = payTo;
207
+ if (x402?.facilitator !== undefined)
208
+ hint.facilitator = x402.facilitator;
209
+ if (x402?.extra !== undefined)
210
+ hint.extra = x402.extra;
211
+ return hint;
212
+ });
213
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Domain/social verification attestations (`Attestation` accounts, #7379) —
3
+ * read WITH the universal staleness rule from docs/record-trust.md
4
+ * structurally enforced, plus instruction builders for `set_attestor` /
5
+ * `create_attestation` / `close_attestation`.
6
+ *
7
+ * Hand-rolled like the rest of this package — no Anchor client, no
8
+ * `@solana/web3.js` import (it stays an optional peer). Builders return the
9
+ * transport-neutral {@link BuiltInstruction} `delegate.ts` defines; PDAs are
10
+ * NOT derived here (see delegate.ts's module docs for why — derive
11
+ * `["attestation", handlePda, kindByte]` / `["attestor_config"]` with your
12
+ * runtime's canonical `findProgramAddress`).
13
+ *
14
+ * # What an attestation proves — and, honestly, what it does not
15
+ *
16
+ * An `Attestation` proves exactly one statement: **"the key configured in
17
+ * `AttestorConfig` — the x1id review process — attested this evidence at
18
+ * time `attestedAt`."** It is NOT a trustless proof of domain or social
19
+ * control: the verification (DNS lookup, social-post check, human review)
20
+ * happens OFF-chain, and the chain records only that the attestor key signed
21
+ * off on it. That key is admin-rotatable, so the trust anchor is "whoever
22
+ * the registry admin currently designates" — rotatable-key trust, not
23
+ * trustlessness. Consumers needing stronger guarantees must not render an
24
+ * attestation as more than it is.
25
+ *
26
+ * # Verified = ONE live attestation of EITHER kind
27
+ *
28
+ * Per the #7145 decision (a single strong signal is enough — requiring two
29
+ * would reject Nike proving control of nike.com), a handle is "verified"
30
+ * when at least one NON-STALE attestation of either kind exists; `kind`
31
+ * records which signal proved it. {@link isHandleVerified} implements
32
+ * exactly this.
33
+ *
34
+ * # Why every function here demands `registeredAt` — epoch-bound, no TTL
35
+ *
36
+ * Attestations do not expire on a timer (owner decision 2026-09-03); they
37
+ * are invalidated by OWNERSHIP EPOCH. A `Handle`'s address is
38
+ * `["handle", name]` — a pure function of the name — so release +
39
+ * re-register lands the new registration at the SAME pubkey, and the
40
+ * previous owner's attestation is physically attached to the new owner's
41
+ * name with no action by anyone. The mandatory read-side rule
42
+ * (docs/record-trust.md, the universal rule):
43
+ *
44
+ * attestation.attested_at >= handle.registered_at
45
+ *
46
+ * An attestation that fails it belongs to a previous, unrelated owner and
47
+ * must never be rendered as verifying the current one. Like `records.ts`,
48
+ * there is deliberately no way to decode or fetch an attestation through
49
+ * this module without the handle's `registered_at` in hand. (The one
50
+ * documented blind spot is shared with `Handle.owner`/`Primary`/
51
+ * `RecordDelegate`: a bearer-NFT marketplace trade bumps no epoch, so the
52
+ * attestation keeps reading live until the attestor re-reviews or revokes.)
53
+ *
54
+ * # `records_cleared_at` (#8139) — the SECOND, independent staleness rule
55
+ *
56
+ * `clear_records` lets an owner bulk-invalidate every record RIGHT NOW,
57
+ * without a transfer — its own doc comment (lib.rs) names attestations
58
+ * explicitly alongside records/text-records as sharing this epoch rule. The
59
+ * same functions therefore also demand the handle's `recordsClearedAt` (0 if
60
+ * never cleared, from `ParsedHandle`), and an attestation is `stale` when
61
+ * EITHER `attested_at < registeredAt` OR `attested_at < recordsClearedAt`.
62
+ */
63
+ import type { AddressLike, BuiltInstruction } from "./delegate.js";
64
+ import type { RpcFn } from "./accounts.js";
65
+ /** Seed prefix of an attestation PDA: `["attestation", handlePda, kindByte]`. */
66
+ export declare const ATTESTATION_SEED = "attestation";
67
+ /** Seed of the attestor-config singleton PDA: `["attestor_config"]`. */
68
+ export declare const ATTESTOR_CONFIG_SEED = "attestor_config";
69
+ /** `Attestation.kind` — domain control proven (DNS TXT challenge). */
70
+ export declare const ATTESTATION_KIND_DNS = 0;
71
+ /** `Attestation.kind` — social-account control proven. */
72
+ export declare const ATTESTATION_KIND_SOCIAL = 1;
73
+ /** Anchor account discriminator: `sha256("account:Attestation")[0..8]`.
74
+ * Pinned (this SDK is zero-dependency and cannot assume WebCrypto SHA-256
75
+ * everywhere it runs); asserted against a re-derivation in the test suite
76
+ * so a typo can never silently pass. */
77
+ export declare const ATTESTATION_DISCRIMINATOR: Uint8Array;
78
+ /** Anchor account discriminator: `sha256("account:AttestorConfig")[0..8]`. */
79
+ export declare const ATTESTOR_CONFIG_DISCRIMINATOR: Uint8Array;
80
+ /** Anchor instruction discriminator: `sha256("global:set_attestor")[0..8]`. */
81
+ export declare const SET_ATTESTOR_DISCRIMINATOR: Uint8Array;
82
+ /** Anchor instruction discriminator: `sha256("global:create_attestation")[0..8]`. */
83
+ export declare const CREATE_ATTESTATION_DISCRIMINATOR: Uint8Array;
84
+ /** Anchor instruction discriminator: `sha256("global:close_attestation")[0..8]`. */
85
+ export declare const CLOSE_ATTESTATION_DISCRIMINATOR: Uint8Array;
86
+ /** `Attestation` account size — every field is fixed-width, so unlike a
87
+ * `Handle` the account is exactly this long:
88
+ * disc(8) + handle(32) + kind(1) + evidence_hash(32) + attested_at(8)
89
+ * + attestor(32) + bump(1). */
90
+ export declare const ATTESTATION_LEN = 114;
91
+ /** `AttestorConfig` account size: disc(8) + attestor(32) + bump(1). */
92
+ export declare const ATTESTOR_CONFIG_LEN = 41;
93
+ /** Which verification signal an attestation `kind` byte names, or null for a
94
+ * kind this SDK does not know (future program versions may add kinds). */
95
+ export declare function attestationKindName(kind: number): "dns" | "social" | null;
96
+ /** A decoded verification attestation, staleness already judged. */
97
+ export interface HandleAttestation {
98
+ /** The Attestation account's address, base58. */
99
+ readonly account: string;
100
+ /** The Handle account this attestation is for, base58. */
101
+ readonly handle: string;
102
+ /** Raw kind byte (0 = dns, 1 = social). */
103
+ readonly kind: number;
104
+ /** Human name for `kind`, or null for an unknown kind. */
105
+ readonly kindName: "dns" | "social" | null;
106
+ /** `sha256` commitment to the off-chain verdict-inputs bundle. */
107
+ readonly evidenceHash: Uint8Array;
108
+ /** Unix seconds the attestation was (last) stamped. */
109
+ readonly attestedAt: bigint;
110
+ /** The attestor key that signed the stamp, base58 — a historical fact,
111
+ * not re-checked against the current `AttestorConfig`. */
112
+ readonly attestor: string;
113
+ /**
114
+ * The rule docs/record-trust.md mandates: this attestation only vouches
115
+ * for the current owner if `attested_at >= handle.registered_at`. A stale
116
+ * attestation belongs to a previous, unrelated owner of the same name and
117
+ * must never be rendered as verifying the current one.
118
+ */
119
+ readonly stale: boolean;
120
+ }
121
+ /**
122
+ * Decode one Attestation account.
123
+ *
124
+ * `registeredAt` is the owning Handle's `registered_at`, decoded by the
125
+ * caller from the Handle account — it decides `stale`. `recordsClearedAt` is
126
+ * that same Handle's `recordsClearedAt` (0 if never cleared, #8139), a
127
+ * SECOND independent staleness anchor. There is intentionally no overload
128
+ * without either (see the module docs).
129
+ *
130
+ * Returns null for anything that is not an Attestation: wrong length or
131
+ * wrong discriminator.
132
+ */
133
+ export declare function decodeAttestation(raw: Uint8Array, account: string, registeredAt: bigint, recordsClearedAt: bigint): HandleAttestation | null;
134
+ /** The attestations that vouch for the CURRENT owner — `stale` ones
135
+ * excluded. This is the list to judge verification from. */
136
+ export declare function liveAttestations(attestations: readonly HandleAttestation[]): HandleAttestation[];
137
+ /**
138
+ * The #7145 verified rule: a handle is verified when at least ONE non-stale
139
+ * attestation of EITHER kind exists (a single strong signal is enough; the
140
+ * surviving `kindName`s say which signals proved it).
141
+ */
142
+ export declare function isHandleVerified(attestations: readonly HandleAttestation[]): boolean;
143
+ /**
144
+ * Fetch every Attestation account of a handle — one `getProgramAccounts`
145
+ * call, filtered by the RPC on size (114), the Attestation discriminator at
146
+ * offset 0 and the handle pubkey at offset 8, then every byte re-checked
147
+ * locally (the node's filters are an optimisation, never the guarantee) —
148
+ * the exact shape of `fetchRecords`. Scoping the scan to the registry
149
+ * program id also IS the ownership check.
150
+ *
151
+ * `registeredAt` is `Handle.registered_at` as decoded from the Handle
152
+ * account the caller already has — the staleness rule needs it, and there
153
+ * is no variant of this function without it. `recordsClearedAt` is that same
154
+ * Handle's `recordsClearedAt` (0 if never cleared, #8139) — pass it through.
155
+ * Every attestation is returned, stale ones flagged, so an owner surface can
156
+ * show what a previous registration left behind; anything that renders a
157
+ * verified badge takes {@link isHandleVerified} / {@link liveAttestations}.
158
+ */
159
+ export declare function fetchAttestations(rpc: RpcFn, programId: string, handleAccount: string, registeredAt: bigint, recordsClearedAt: bigint): Promise<HandleAttestation[]>;
160
+ export interface SetAttestorParams {
161
+ /** The registry program id. */
162
+ readonly programId: AddressLike;
163
+ /** The registry admin (`Config.admin`). Signer; also pays the one-time
164
+ * `AttestorConfig` init. */
165
+ readonly admin: AddressLike;
166
+ /** The `["config"]` PDA. */
167
+ readonly config: AddressLike;
168
+ /** The `["attestor_config"]` PDA — derive per the module docs. */
169
+ readonly attestorConfig: AddressLike;
170
+ /** The key being granted attestation-signing authority. */
171
+ readonly attestor: AddressLike;
172
+ }
173
+ /**
174
+ * Build `set_attestor` — admin-only: initialize or rotate the attestor key.
175
+ * Rotation does not void existing attestations (they record their signer as
176
+ * a historical fact); revoking a bad key's output is `close_attestation`.
177
+ */
178
+ export declare function buildSetAttestorIx(p: SetAttestorParams): BuiltInstruction;
179
+ export interface CreateAttestationParams {
180
+ /** The registry program id. */
181
+ readonly programId: AddressLike;
182
+ /** The configured attestor. Signer; pays the attestation's rent on first
183
+ * stamp. */
184
+ readonly attestor: AddressLike;
185
+ /** The `["attestor_config"]` PDA. */
186
+ readonly attestorConfig: AddressLike;
187
+ /** The `["handle", name]` PDA being attested (must be registered). */
188
+ readonly handle: AddressLike;
189
+ /** The `["attestation", handle, kindByte]` PDA — derive per the module
190
+ * docs, with the SAME kind byte passed below. */
191
+ readonly attestation: AddressLike;
192
+ /** {@link ATTESTATION_KIND_DNS} or {@link ATTESTATION_KIND_SOCIAL}. */
193
+ readonly kind: number;
194
+ /** `sha256` of the off-chain verdict-inputs bundle (32 bytes) — a
195
+ * commitment, never the raw evidence. */
196
+ readonly evidenceHash: Uint8Array;
197
+ }
198
+ /**
199
+ * Build `create_attestation` — attestor-only: stamp (or re-stamp,
200
+ * re-deriving `attested_at` from the Clock and replacing the evidence hash)
201
+ * the (handle, kind) attestation.
202
+ */
203
+ export declare function buildCreateAttestationIx(p: CreateAttestationParams): BuiltInstruction;
204
+ export interface CloseAttestationParams {
205
+ /** The registry program id. */
206
+ readonly programId: AddressLike;
207
+ /** The current attestor OR the admin. Signer. */
208
+ readonly signer: AddressLike;
209
+ /** The `["config"]` PDA. */
210
+ readonly config: AddressLike;
211
+ /** The `["attestor_config"]` PDA. */
212
+ readonly attestorConfig: AddressLike;
213
+ /** The attestation account being closed. No Handle account is needed —
214
+ * the program re-derives the seeds from the attestation's own stored
215
+ * fields, so a stale attestation stranded by a released handle stays
216
+ * revocable. */
217
+ readonly attestation: AddressLike;
218
+ /** Receives the closed account's rent — any account the caller chooses. */
219
+ readonly recipient: AddressLike;
220
+ }
221
+ /**
222
+ * Build `close_attestation` — revoke: close the account, rent to
223
+ * `recipient`. Attestor- or admin-signed (the admin path is the cleanup for
224
+ * a rotated-away key's output).
225
+ */
226
+ export declare function buildCloseAttestationIx(p: CloseAttestationParams): BuiltInstruction;