@x1id/resolve 0.22.0 → 0.43.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.
Files changed (51) hide show
  1. package/README.md +71 -0
  2. package/dist/accounts.d.ts +6 -0
  3. package/dist/accounts.js +6 -1
  4. package/dist/agent.d.ts +184 -8
  5. package/dist/agent.js +351 -3
  6. package/dist/agentPolicy.d.ts +238 -0
  7. package/dist/agentPolicy.js +364 -0
  8. package/dist/attestation.d.ts +25 -0
  9. package/dist/attestation.js +25 -0
  10. package/dist/checkout.d.ts +216 -0
  11. package/dist/checkout.js +327 -0
  12. package/dist/credentials.d.ts +466 -0
  13. package/dist/credentials.js +669 -0
  14. package/dist/gate.d.ts +94 -4
  15. package/dist/gate.js +206 -7
  16. package/dist/gateClient.d.ts +12 -0
  17. package/dist/giftLink.d.ts +272 -0
  18. package/dist/giftLink.js +452 -0
  19. package/dist/index.d.ts +14 -0
  20. package/dist/index.js +33 -0
  21. package/dist/loginButton.d.ts +69 -0
  22. package/dist/loginButton.js +207 -0
  23. package/dist/logo.d.ts +1 -0
  24. package/dist/logo.js +8 -0
  25. package/dist/messaging.d.ts +439 -0
  26. package/dist/messaging.js +979 -0
  27. package/dist/mintNft.d.ts +247 -0
  28. package/dist/mintNft.js +328 -0
  29. package/dist/nsGate.d.ts +98 -0
  30. package/dist/nsGate.js +172 -0
  31. package/dist/oidc.d.ts +272 -0
  32. package/dist/oidc.js +641 -0
  33. package/dist/pay.d.ts +513 -0
  34. package/dist/pay.js +756 -0
  35. package/dist/payment.d.ts +9 -2
  36. package/dist/payment.js +9 -2
  37. package/dist/recovery.d.ts +186 -0
  38. package/dist/recovery.js +276 -0
  39. package/dist/recoveryNft.d.ts +183 -0
  40. package/dist/recoveryNft.js +291 -0
  41. package/dist/shield.d.ts +373 -0
  42. package/dist/shield.js +695 -0
  43. package/dist/subscription.d.ts +439 -0
  44. package/dist/subscription.js +642 -0
  45. package/dist/textRecords.d.ts +52 -6
  46. package/dist/textRecords.js +80 -8
  47. package/dist/types.d.ts +3 -0
  48. package/dist/x402Receipt.d.ts +246 -0
  49. package/dist/x402Receipt.js +374 -0
  50. package/package.json +18 -1
  51. package/schema/agent-manifest.json +91 -8
package/dist/gate.d.ts CHANGED
@@ -13,17 +13,50 @@
13
13
  * {@link markHolderMismatch} onto the fetched set never admits an inherited
14
14
  * badge. Nothing here trusts an off-chain claim: every input is an on-chain,
15
15
  * attestor-signed `Attestation` account.
16
+ *
17
+ * # Third-party credentials (upgrade 6, docs/specs/x1id-credentials.md)
18
+ *
19
+ * A gate may also require an issuer-signed credential:
20
+ * `{ credential: { schema, issuers, minRemainingSecs? } }`, combined with
21
+ * `{ allOf: Gate[] }` / `{ anyOf: Gate[] }`. `issuers` is an explicit, non-empty
22
+ * list of Issuer PDAs, or the alias `"x1id-recommended"` (X1ID's ADVISORY
23
+ * list, which an app must choose; {@link resolveRecommendedIssuers} snapshots
24
+ * it into a fixed list). `"any-verified"`, `{ kind }` and `{ anyOf: kinds }`
25
+ * keep reading ONLY attestations: a credential never satisfies the blue tick.
16
26
  */
17
27
  import { type HandleAttestation } from "./attestation.js";
18
28
  import type { RpcFn } from "./accounts.js";
29
+ import type { PdaFinder } from "./namespacePricing.js";
30
+ import { type HandleCredential } from "./credentials.js";
31
+ /** The alias for X1ID's advisory recommended-issuer list. */
32
+ export declare const RECOMMENDED_ISSUERS_ALIAS = "x1id-recommended";
33
+ /** Opt-in to follow the live list instead of a snapshot (dev-api policies). */
34
+ export declare const RECOMMENDED_ISSUERS_LIVE_ALIAS = "x1id-recommended:live";
35
+ /** A third-party credential requirement. */
36
+ export interface CredentialClause {
37
+ /** The issuer-defined schema id (u32). */
38
+ readonly schema: number;
39
+ /** Issuer PDAs (base58, 1-32), or the advisory-list alias. */
40
+ readonly issuers: readonly string[] | typeof RECOMMENDED_ISSUERS_ALIAS | typeof RECOMMENDED_ISSUERS_LIVE_ALIAS;
41
+ /** Demand at least this much validity left (seconds, >= 0). */
42
+ readonly minRemainingSecs?: number;
43
+ }
19
44
  /** What a handle must prove to pass the gate:
20
45
  * - `"any-verified"` — at least one non-stale attestation of any kind.
21
46
  * - `{ kind }` — a non-stale attestation of exactly that kind.
22
- * - `{ anyOf }` — a non-stale attestation of any listed kind (e.g. X OR GitHub). */
47
+ * - `{ anyOf: kinds }` — a non-stale attestation of any listed kind (e.g. X OR GitHub).
48
+ * - `{ credential }` — a valid third-party credential (see {@link CredentialClause}).
49
+ * - `{ allOf: Gate[] }` / `{ anyOf: Gate[] }` — every / any sub-gate. */
23
50
  export type Gate = "any-verified" | {
24
51
  readonly kind: number;
25
52
  } | {
26
53
  readonly anyOf: readonly number[];
54
+ } | {
55
+ readonly credential: CredentialClause;
56
+ } | {
57
+ readonly allOf: readonly Gate[];
58
+ } | {
59
+ readonly anyOf: readonly Gate[];
27
60
  };
28
61
  /** Named platform kinds, re-exported so callers gate without magic numbers. */
29
62
  export declare const GateKind: {
@@ -34,17 +67,74 @@ export declare const GateKind: {
34
67
  readonly github: 4;
35
68
  readonly telegram: 5;
36
69
  };
70
+ /**
71
+ * Validate a gate's shape (throws a typed `ResolveError("invalid-input")`).
72
+ * Credential clauses need a u32 `schema`, a non-negative integer
73
+ * `minRemainingSecs`, and `issuers` that is the alias or 1-32 distinct
74
+ * base58 addresses. Nesting is bounded (depth 4, 16 clauses).
75
+ */
76
+ export declare function validateGate(gate: Gate): void;
77
+ /** Every credential clause in `gate` (depth-first). */
78
+ export declare function credentialClauses(gate: Gate): CredentialClause[];
79
+ /**
80
+ * Replace the `"x1id-recommended"` alias with a fixed copy of `list` (a
81
+ * snapshot): a later change of the advisory list, or a compromised admin key
82
+ * adding an issuer, then cannot change this gate silently. The
83
+ * `"x1id-recommended:live"` alias is kept unless `resolveLive` is set.
84
+ * Throws if the list is empty (a gate must name at least one issuer).
85
+ */
86
+ export declare function resolveRecommendedIssuers(gate: Gate, list: readonly string[], opts?: {
87
+ readonly resolveLive?: boolean;
88
+ }): Gate;
89
+ /** Inputs for evaluating credential clauses. */
90
+ export interface CredentialGateInputs {
91
+ /** `{ schema, issuers }` → the credentials found (any validity). */
92
+ readonly credentialsFor: (clause: CredentialClause) => readonly HandleCredential[];
93
+ }
94
+ /** Which clauses passed, for display. */
95
+ export interface GateEvaluation {
96
+ readonly passed: boolean;
97
+ /** Human-readable ids of the satisfied leaf clauses (`kind:4`,
98
+ * `credential:<issuer>:<schema>`, `any-verified`). */
99
+ readonly matched: readonly string[];
100
+ }
101
+ /**
102
+ * Pure evaluation of any gate. Attestation clauses read `attestations` only;
103
+ * credential clauses read `credentials` (required when the gate has any).
104
+ * Stale and holder-mismatched attestations never count; a credential counts
105
+ * only when `validity.valid` (credentials.ts `credentialValidity`).
106
+ */
107
+ export declare function evaluateGate(gate: Gate, attestations: readonly HandleAttestation[], credentials?: CredentialGateInputs): GateEvaluation;
37
108
  /** Pure check: do these (already-fetched) attestations satisfy the gate?
38
- * Stale and holder-mismatched (H5) attestations never count. */
109
+ * Stale and holder-mismatched (H5) attestations never count. A gate with a
110
+ * credential clause throws here: use {@link evaluateGate} with credentials. */
39
111
  export declare function attestationsPassGate(attestations: readonly HandleAttestation[], gate: Gate): boolean;
112
+ /** Extra inputs `handlePassesGate` needs for credential clauses. */
113
+ export interface CredentialGateOptions {
114
+ /** `find_program_address` (see namespacePricing.ts `PdaFinder`). */
115
+ readonly find: PdaFinder;
116
+ /** The handle's CURRENT authority: `Handle.owner` while untokenized, the
117
+ * pNFT holder once tokenized. */
118
+ readonly currentHolder: string | null;
119
+ /** Unix seconds (default: now). */
120
+ readonly nowSecs?: bigint | number;
121
+ /** A pre-fetched advisory list for the alias (default: read from chain). */
122
+ readonly recommendedIssuers?: readonly string[];
123
+ }
40
124
  /** Fetch a handle's attestations and test the gate in one call. `handleAccount`,
41
125
  * `registeredAt`, `recordsClearedAt` and `holderEpoch` come from the handle you
42
126
  * resolved (name → `deriveHandleAccount` → decode). `holderEpoch` (H5 / PR
43
127
  * #466, 0 if absent) folds into the staleness epoch so the gate matches the
44
128
  * on-chain rule `attested_at >= max(registered_at, holder_epoch)`; it defaults
45
129
  * to `0n` for backward compatibility. Returns the pass/fail plus the
46
- * attestations so a caller can also render WHICH signals matched. */
47
- export declare function handlePassesGate(rpc: RpcFn, programId: string, handleAccount: string, registeredAt: bigint, recordsClearedAt: bigint, gate: Gate, holderEpoch?: bigint): Promise<{
130
+ * attestations so a caller can also render WHICH signals matched.
131
+ *
132
+ * A gate with credential clauses also needs `credentialOpts` (the PDA
133
+ * finder and the current holder); its credentials are read from the chain
134
+ * (derived PDAs, one `getMultipleAccounts` per clause) and returned too. */
135
+ export declare function handlePassesGate(rpc: RpcFn, programId: string, handleAccount: string, registeredAt: bigint, recordsClearedAt: bigint, gate: Gate, holderEpoch?: bigint, credentialOpts?: CredentialGateOptions): Promise<{
48
136
  readonly passed: boolean;
49
137
  readonly attestations: HandleAttestation[];
138
+ readonly credentials?: readonly HandleCredential[];
139
+ readonly matched?: readonly string[];
50
140
  }>;
package/dist/gate.js CHANGED
@@ -13,9 +13,25 @@
13
13
  * {@link markHolderMismatch} onto the fetched set never admits an inherited
14
14
  * badge. Nothing here trusts an off-chain claim: every input is an on-chain,
15
15
  * attestor-signed `Attestation` account.
16
+ *
17
+ * # Third-party credentials (upgrade 6, docs/specs/x1id-credentials.md)
18
+ *
19
+ * A gate may also require an issuer-signed credential:
20
+ * `{ credential: { schema, issuers, minRemainingSecs? } }`, combined with
21
+ * `{ allOf: Gate[] }` / `{ anyOf: Gate[] }`. `issuers` is an explicit, non-empty
22
+ * list of Issuer PDAs, or the alias `"x1id-recommended"` (X1ID's ADVISORY
23
+ * list, which an app must choose; {@link resolveRecommendedIssuers} snapshots
24
+ * it into a fixed list). `"any-verified"`, `{ kind }` and `{ anyOf: kinds }`
25
+ * keep reading ONLY attestations: a credential never satisfies the blue tick.
16
26
  */
17
27
  import { fetchAttestations, isHandleVerified, ATTESTATION_KIND_DNS, ATTESTATION_KIND_SOCIAL, ATTESTATION_KIND_X, ATTESTATION_KIND_DISCORD, ATTESTATION_KIND_GITHUB, ATTESTATION_KIND_TELEGRAM, } from "./attestation.js";
18
28
  import { ResolveError } from "./types.js";
29
+ import { decodeBase58_32 } from "./base58.js";
30
+ import { fetchHandleCredentials, fetchRecommendedIssuers, MAX_RECOMMENDED_ISSUERS, } from "./credentials.js";
31
+ /** The alias for X1ID's advisory recommended-issuer list. */
32
+ export const RECOMMENDED_ISSUERS_ALIAS = "x1id-recommended";
33
+ /** Opt-in to follow the live list instead of a snapshot (dev-api policies). */
34
+ export const RECOMMENDED_ISSUERS_LIVE_ALIAS = "x1id-recommended:live";
19
35
  /** Named platform kinds, re-exported so callers gate without magic numbers. */
20
36
  export const GateKind = {
21
37
  dns: ATTESTATION_KIND_DNS,
@@ -25,8 +41,166 @@ export const GateKind = {
25
41
  github: ATTESTATION_KIND_GITHUB,
26
42
  telegram: ATTESTATION_KIND_TELEGRAM,
27
43
  };
44
+ const MAX_GATE_DEPTH = 4;
45
+ const MAX_GATE_NODES = 16;
46
+ function bad(msg, detail) {
47
+ throw new ResolveError("invalid-input", msg, typeof detail === "string" ? detail : JSON.stringify(detail));
48
+ }
49
+ /**
50
+ * Validate a gate's shape (throws a typed `ResolveError("invalid-input")`).
51
+ * Credential clauses need a u32 `schema`, a non-negative integer
52
+ * `minRemainingSecs`, and `issuers` that is the alias or 1-32 distinct
53
+ * base58 addresses. Nesting is bounded (depth 4, 16 clauses).
54
+ */
55
+ export function validateGate(gate) {
56
+ let nodes = 0;
57
+ const walk = (g, depth) => {
58
+ if (++nodes > MAX_GATE_NODES)
59
+ bad("gate has too many clauses (max 16)", g);
60
+ if (depth > MAX_GATE_DEPTH)
61
+ bad("gate is nested too deeply (max 4)", g);
62
+ if (g === "any-verified")
63
+ return;
64
+ if (g === null || typeof g !== "object" || Array.isArray(g)) {
65
+ bad('gate must be "any-verified", { kind }, { anyOf }, { allOf } or { credential }', String(g));
66
+ }
67
+ const o = g;
68
+ if ("kind" in o) {
69
+ if (typeof o.kind !== "number")
70
+ bad("kind must be a number", g);
71
+ return;
72
+ }
73
+ if ("anyOf" in o || "allOf" in o) {
74
+ const list = ("anyOf" in o ? o.anyOf : o.allOf);
75
+ if (!Array.isArray(list))
76
+ bad("anyOf / allOf needs an array", g);
77
+ // `{ anyOf: kinds }` keeps its pre-credentials meaning (an empty list
78
+ // simply never passes).
79
+ if ("anyOf" in o && list.every((x) => typeof x === "number"))
80
+ return;
81
+ if (list.length === 0)
82
+ bad("allOf / anyOf of gates needs at least one gate", g);
83
+ for (const sub of list)
84
+ walk(sub, depth + 1);
85
+ return;
86
+ }
87
+ if ("credential" in o) {
88
+ const c = o.credential;
89
+ if (c === null || typeof c !== "object")
90
+ bad("credential clause must be an object", g);
91
+ if (!Number.isInteger(c.schema) || c.schema < 0 || c.schema > 0xffff_ffff)
92
+ bad("credential.schema must be a u32", g);
93
+ if (c.minRemainingSecs !== undefined && (!Number.isSafeInteger(c.minRemainingSecs) || c.minRemainingSecs < 0)) {
94
+ bad("credential.minRemainingSecs must be a non-negative integer", g);
95
+ }
96
+ const iss = c.issuers;
97
+ if (iss === RECOMMENDED_ISSUERS_ALIAS || iss === RECOMMENDED_ISSUERS_LIVE_ALIAS)
98
+ return;
99
+ if (!Array.isArray(iss) || iss.length === 0)
100
+ bad("credential.issuers must be a non-empty list or \"x1id-recommended\"", g);
101
+ if (iss.length > MAX_RECOMMENDED_ISSUERS)
102
+ bad("credential.issuers has more than 32 entries", g);
103
+ if (new Set(iss).size !== iss.length)
104
+ bad("credential.issuers has duplicates", g);
105
+ for (const a of iss)
106
+ if (typeof a !== "string" || !decodeBase58_32(a))
107
+ bad("credential.issuers entries must be base58 addresses", a);
108
+ return;
109
+ }
110
+ bad("gate object needs a `kind`, `anyOf`, `allOf` or `credential`", g);
111
+ };
112
+ walk(gate, 0);
113
+ }
114
+ /** Every credential clause in `gate` (depth-first). */
115
+ export function credentialClauses(gate) {
116
+ const out = [];
117
+ const walk = (g) => {
118
+ if (g === "any-verified" || typeof g !== "object" || g === null)
119
+ return;
120
+ if ("credential" in g)
121
+ out.push(g.credential);
122
+ else if ("allOf" in g)
123
+ g.allOf.forEach(walk);
124
+ else if ("anyOf" in g && !g.anyOf.every((x) => typeof x === "number"))
125
+ g.anyOf.forEach(walk);
126
+ };
127
+ walk(gate);
128
+ return out;
129
+ }
130
+ /**
131
+ * Replace the `"x1id-recommended"` alias with a fixed copy of `list` (a
132
+ * snapshot): a later change of the advisory list, or a compromised admin key
133
+ * adding an issuer, then cannot change this gate silently. The
134
+ * `"x1id-recommended:live"` alias is kept unless `resolveLive` is set.
135
+ * Throws if the list is empty (a gate must name at least one issuer).
136
+ */
137
+ export function resolveRecommendedIssuers(gate, list, opts = {}) {
138
+ const map = (g) => {
139
+ if (g === "any-verified" || typeof g !== "object" || g === null)
140
+ return g;
141
+ if ("credential" in g) {
142
+ const iss = g.credential.issuers;
143
+ const alias = iss === RECOMMENDED_ISSUERS_ALIAS || (opts.resolveLive === true && iss === RECOMMENDED_ISSUERS_LIVE_ALIAS);
144
+ if (!alias)
145
+ return g;
146
+ if (list.length === 0)
147
+ bad("the recommended issuer list is empty", g);
148
+ return { credential: { ...g.credential, issuers: [...list] } };
149
+ }
150
+ if ("allOf" in g)
151
+ return { allOf: g.allOf.map(map) };
152
+ if ("anyOf" in g && !g.anyOf.every((x) => typeof x === "number"))
153
+ return { anyOf: g.anyOf.map(map) };
154
+ return g;
155
+ };
156
+ return map(gate);
157
+ }
158
+ /**
159
+ * Pure evaluation of any gate. Attestation clauses read `attestations` only;
160
+ * credential clauses read `credentials` (required when the gate has any).
161
+ * Stale and holder-mismatched attestations never count; a credential counts
162
+ * only when `validity.valid` (credentials.ts `credentialValidity`).
163
+ */
164
+ export function evaluateGate(gate, attestations, credentials) {
165
+ validateGate(gate);
166
+ const matched = [];
167
+ const evalNode = (g) => {
168
+ if (g === "any-verified") {
169
+ const ok = isHandleVerified(attestations);
170
+ if (ok)
171
+ matched.push("any-verified");
172
+ return ok;
173
+ }
174
+ if ("kind" in g)
175
+ return kinds([g.kind]);
176
+ if ("credential" in g) {
177
+ if (!credentials)
178
+ bad("a credential gate needs credential inputs (use handlePassesGate with opts, or evaluateGate with credentials)", g);
179
+ const c = g.credential;
180
+ if (!Array.isArray(c.issuers))
181
+ bad("resolve the \"x1id-recommended\" alias first (resolveRecommendedIssuers)", g);
182
+ const hit = credentials.credentialsFor(c).find((h) => h.validity.valid && c.issuers.includes(h.credential.issuer));
183
+ if (hit)
184
+ matched.push(`credential:${hit.credential.issuer}:${c.schema}`);
185
+ return hit !== undefined;
186
+ }
187
+ if ("allOf" in g)
188
+ return g.allOf.map(evalNode).every(Boolean);
189
+ if (g.anyOf.every((x) => typeof x === "number"))
190
+ return kinds(g.anyOf);
191
+ return g.anyOf.map(evalNode).some(Boolean);
192
+ };
193
+ const kinds = (ks) => {
194
+ const hit = attestations.find((a) => !a.stale && !a.holderMismatch && ks.includes(a.kind));
195
+ if (hit)
196
+ matched.push(`kind:${hit.kind}`);
197
+ return hit !== undefined;
198
+ };
199
+ return { passed: evalNode(gate), matched };
200
+ }
28
201
  /** Pure check: do these (already-fetched) attestations satisfy the gate?
29
- * Stale and holder-mismatched (H5) attestations never count. */
202
+ * Stale and holder-mismatched (H5) attestations never count. A gate with a
203
+ * credential clause throws here: use {@link evaluateGate} with credentials. */
30
204
  export function attestationsPassGate(attestations, gate) {
31
205
  if (gate === "any-verified")
32
206
  return isHandleVerified(attestations);
@@ -36,11 +210,10 @@ export function attestationsPassGate(attestations, gate) {
36
210
  if (gate === null || typeof gate !== "object") {
37
211
  throw new ResolveError("invalid-input", 'gate must be "any-verified", { kind } or { anyOf: [...] }', String(gate));
38
212
  }
39
- const kinds = "kind" in gate ? [gate.kind] : "anyOf" in gate ? gate.anyOf : null;
40
- if (kinds === null) {
213
+ if (!("kind" in gate) && !("anyOf" in gate) && !("allOf" in gate) && !("credential" in gate)) {
41
214
  throw new ResolveError("invalid-input", "gate object needs a `kind` or `anyOf`", JSON.stringify(gate));
42
215
  }
43
- return attestations.some((a) => !a.stale && !a.holderMismatch && kinds.includes(a.kind));
216
+ return evaluateGate(gate, attestations).passed;
44
217
  }
45
218
  /** Fetch a handle's attestations and test the gate in one call. `handleAccount`,
46
219
  * `registeredAt`, `recordsClearedAt` and `holderEpoch` come from the handle you
@@ -48,8 +221,34 @@ export function attestationsPassGate(attestations, gate) {
48
221
  * #466, 0 if absent) folds into the staleness epoch so the gate matches the
49
222
  * on-chain rule `attested_at >= max(registered_at, holder_epoch)`; it defaults
50
223
  * to `0n` for backward compatibility. Returns the pass/fail plus the
51
- * attestations so a caller can also render WHICH signals matched. */
52
- export async function handlePassesGate(rpc, programId, handleAccount, registeredAt, recordsClearedAt, gate, holderEpoch = 0n) {
224
+ * attestations so a caller can also render WHICH signals matched.
225
+ *
226
+ * A gate with credential clauses also needs `credentialOpts` (the PDA
227
+ * finder and the current holder); its credentials are read from the chain
228
+ * (derived PDAs, one `getMultipleAccounts` per clause) and returned too. */
229
+ export async function handlePassesGate(rpc, programId, handleAccount, registeredAt, recordsClearedAt, gate, holderEpoch = 0n, credentialOpts) {
230
+ validateGate(gate);
53
231
  const attestations = await fetchAttestations(rpc, programId, handleAccount, registeredAt, recordsClearedAt, holderEpoch);
54
- return { passed: attestationsPassGate(attestations, gate), attestations };
232
+ const clauses = credentialClauses(gate);
233
+ if (clauses.length === 0)
234
+ return { passed: attestationsPassGate(attestations, gate), attestations };
235
+ if (!credentialOpts) {
236
+ throw new ResolveError("invalid-input", "a credential gate needs credentialOpts { find, currentHolder }", JSON.stringify(gate));
237
+ }
238
+ const needsList = clauses.some((c) => !Array.isArray(c.issuers));
239
+ const list = needsList ? (credentialOpts.recommendedIssuers ?? (await fetchRecommendedIssuers(rpc, credentialOpts.find, programId))) : [];
240
+ const resolved = needsList ? resolveRecommendedIssuers(gate, list, { resolveLive: true }) : gate;
241
+ const now = credentialOpts.nowSecs ?? BigInt(Math.floor(Date.now() / 1000));
242
+ const ctx = {
243
+ handle: handleAccount,
244
+ registeredAt,
245
+ holderEpoch,
246
+ currentHolder: credentialOpts.currentHolder,
247
+ };
248
+ const found = new Map();
249
+ for (const c of credentialClauses(resolved)) {
250
+ found.set(c, await fetchHandleCredentials(rpc, credentialOpts.find, programId, ctx, c.schema, c.issuers, now, c.minRemainingSecs ?? 0));
251
+ }
252
+ const ev = evaluateGate(resolved, attestations, { credentialsFor: (c) => found.get(c) ?? [] });
253
+ return { passed: ev.passed, attestations, credentials: [...found.values()].flat(), matched: ev.matched };
55
254
  }
@@ -41,6 +41,16 @@ export interface GateAttestationView {
41
41
  readonly kind: number;
42
42
  readonly attested_at: number;
43
43
  }
44
+ /** One third-party credential the decision looked at. */
45
+ export interface GateCredentialView {
46
+ readonly issuer: string;
47
+ readonly schema: number;
48
+ readonly subject: string;
49
+ readonly issued_at: number;
50
+ readonly expires_at: number;
51
+ readonly valid: boolean;
52
+ readonly invalid_reasons: readonly string[];
53
+ }
44
54
  /** The decision the service returned. */
45
55
  export interface GateCheckResult {
46
56
  readonly passed: boolean;
@@ -50,6 +60,8 @@ export interface GateCheckResult {
50
60
  };
51
61
  readonly policy: Gate;
52
62
  readonly attestations: readonly GateAttestationView[];
63
+ /** Credentials read for a policy with `{ credential }` clauses (upgrade 6). */
64
+ readonly credentials?: readonly GateCredentialView[];
53
65
  readonly reason?: "no-primary-handle" | "handle-not-registered";
54
66
  }
55
67
  /** Thrown when the decision could not be obtained (network, auth, 5xx, bad
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Open gift links (docs/specs/gift-links.md): send XNT or a prepaid `@name` to
3
+ * whoever opens a link. Instruction builders, the `GiftLink` decoder, the PDA
4
+ * finder and the client helpers that make / parse the link itself.
5
+ *
6
+ * # How it works
7
+ *
8
+ * 1. The sender's client generates a fresh EPHEMERAL ed25519 keypair
9
+ * ({@link newGiftLinkKey}). Its public key is the `gift_key`; its secret
10
+ * goes ONLY into the link's URL fragment ({@link giftLinkUrl}):
11
+ * `https://app.x1id.io/gift/link#k=<base58 64-byte secret key>`. A fragment
12
+ * is never sent to a server.
13
+ * 2. `create_gift_link` (sender + gift key sign) escrows the value under
14
+ * `["gift", gift_key]` ({@link findGiftLinkPda}).
15
+ * 3. The recipient opens the link ({@link parseGiftLinkUrl}) and submits
16
+ * `claim_gift_link` (XNT) or `claim_gift_link_name` (a name, minted as a
17
+ * pNFT) SIGNED BY THE GIFT KEY, naming their own wallet as destination.
18
+ * Any wallet may pay the fee (a relayer works). Because the destination is
19
+ * part of the gift-key-signed message, nobody who sees the claim in flight
20
+ * can redirect it (unlike the removed open vouchers, audit S6).
21
+ * 4. Unclaimed: the sender may cancel ({@link buildRefundGiftLinkIx}) any time;
22
+ * after `expiresAt` anyone may return it to the sender.
23
+ *
24
+ * Whoever holds the link can claim the gift: treat it like cash. Never reuse a
25
+ * gift key (generate one per link).
26
+ *
27
+ * Routed through the program's `fallback` (`extra_ix`): `sha256("global:<name>")
28
+ * [..8]` + raw Borsh, accounts exactly in the order of the program's
29
+ * `Accounts` structs. Hand-rolled, no web3.js. Key generation uses WebCrypto
30
+ * Ed25519 (Node 20+, current browsers); pass your own key if unavailable.
31
+ */
32
+ import type { AddressLike, BuiltInstruction, InstructionKey } from "./delegate.js";
33
+ import type { RpcFn } from "./accounts.js";
34
+ import type { PdaFinder } from "./namespacePricing.js";
35
+ import { type TokenizedTailAccounts } from "./mintNft.js";
36
+ import { type HandleTypeValue } from "./voucher.js";
37
+ /** Seed prefix of the escrow PDA: `["gift", gift_key]`. */
38
+ export declare const GIFT_LINK_SEED = "gift";
39
+ /** Allocated size of a `GiftLink` (8-byte discriminator + `INIT_SPACE`). */
40
+ export declare const GIFT_LINK_ACCOUNT_LEN: number;
41
+ /** Longest a gift link may stay open (same cap as vouchers: 366 days). */
42
+ export declare const MAX_GIFT_LINK_DURATION_SECS: number;
43
+ /** Where the app opens gift links. The secret goes after `#k=`. */
44
+ export declare const GIFT_LINK_BASE_URL = "https://app.x1id.io/gift/link";
45
+ /** A sensible client default for `expiresAt`: 30 days. */
46
+ export declare const DEFAULT_GIFT_LINK_TTL_SECS: number;
47
+ /** Compute-unit limit to request for `claim_gift_link_name` (measured
48
+ * ~307k on litesvm with the real Metaplex program; same headroom as
49
+ * `REGISTER_TOKENIZED_CU_LIMIT`). */
50
+ export declare const CLAIM_GIFT_LINK_NAME_CU_LIMIT = 420000;
51
+ /** `GiftLink.kind` (Borsh enum index). */
52
+ export declare const GIFT_KIND: {
53
+ readonly Xnt: 0;
54
+ readonly Name: 1;
55
+ };
56
+ export type GiftKindName = "xnt" | "name";
57
+ /** `sha256("global:create_gift_link")[..8]`. */
58
+ export declare const CREATE_GIFT_LINK_DISCRIMINATOR: Uint8Array;
59
+ /** `sha256("global:claim_gift_link")[..8]`. */
60
+ export declare const CLAIM_GIFT_LINK_DISCRIMINATOR: Uint8Array;
61
+ /** `sha256("global:claim_gift_link_name")[..8]`. */
62
+ export declare const CLAIM_GIFT_LINK_NAME_DISCRIMINATOR: Uint8Array;
63
+ /** `sha256("global:refund_gift_link")[..8]`. */
64
+ export declare const REFUND_GIFT_LINK_DISCRIMINATOR: Uint8Array;
65
+ /** `sha256("account:GiftLink")[..8]`. */
66
+ export declare const GIFT_LINK_ACCOUNT_DISCRIMINATOR: Uint8Array;
67
+ /** Byte offsets inside a raw `GiftLink` (fixed up to `integrator`), for
68
+ * `getProgramAccounts` `memcmp` filters (e.g. a sender's open links). */
69
+ export declare const GIFT_LINK_OFFSETS: {
70
+ readonly version: 8;
71
+ readonly sender: 9;
72
+ readonly giftKey: 41;
73
+ readonly kind: 73;
74
+ readonly amount: 74;
75
+ readonly name: 82;
76
+ readonly nameLen: 114;
77
+ readonly handleType: 115;
78
+ readonly integrator: 116;
79
+ };
80
+ /** Program error codes added for gift links (`RegistryError`). Gift links
81
+ * also reuse `VoucherNameRegistered` (6057), `BadVoucherExpiry` (6058),
82
+ * `PriceBelowRent` (6086), `ProtocolLockedDown` (6102) and
83
+ * `VoucherExpiryTooFar` (6112). */
84
+ export declare const GIFT_LINK_ERRORS: {
85
+ readonly GiftAmountInvalid: 6151;
86
+ readonly GiftLinkExpired: 6152;
87
+ readonly GiftLinkWrongKind: 6153;
88
+ readonly GiftLinkBadDestination: 6154;
89
+ readonly GiftLinkNotRefundable: 6155;
90
+ readonly GiftLinkNameReserved: 6156;
91
+ /** `gift_key == sender` (Sentinel run-011; 6158-6229 are upgrade 6's). */
92
+ readonly GiftLinkKeyIsSender: 6231;
93
+ };
94
+ /** An ephemeral gift key: `secretKey` is the 64-byte Solana form (32-byte
95
+ * seed ‖ 32-byte public key), so `Keypair.fromSecretKey(secretKey)` (web3.js)
96
+ * signs the claim directly. */
97
+ export interface GiftLinkKey {
98
+ /** base58 public key = the `gift_key`. */
99
+ readonly giftKey: string;
100
+ readonly secretKey: Uint8Array;
101
+ }
102
+ /** Derive the ed25519 public key of a 32-byte seed (WebCrypto). */
103
+ export declare function giftKeyFromSeed(seed: Uint8Array): Promise<GiftLinkKey>;
104
+ /**
105
+ * Generate a fresh ephemeral gift key (CSPRNG seed, WebCrypto Ed25519). One
106
+ * per link; never reuse it.
107
+ */
108
+ export declare function newGiftLinkKey(): Promise<GiftLinkKey>;
109
+ /** The shareable link: `<base>#k=<base58 64-byte secret key>`. The secret is
110
+ * in the FRAGMENT so it never reaches a server log. */
111
+ export declare function giftLinkUrl(key: Pick<GiftLinkKey, "secretKey">, base?: string): string;
112
+ /**
113
+ * Read the gift key back out of a link (or a bare `#k=...` fragment). Accepts
114
+ * the 64-byte secret key (what {@link giftLinkUrl} writes) or a 32-byte seed.
115
+ * Re-derives the public key (WebCrypto) and, for the 64-byte form, refuses a
116
+ * secret whose embedded public key does not match its seed. Null when the
117
+ * link has no well-formed key.
118
+ */
119
+ export declare function parseGiftLinkUrl(link: string): Promise<GiftLinkKey | null>;
120
+ /** The escrow: `["gift", gift_key]`. */
121
+ export declare function findGiftLinkPda(find: PdaFinder, programId: AddressLike, giftKey: AddressLike): Promise<string>;
122
+ /** Decoded `GiftLink`. Addresses are base58. */
123
+ export interface GiftLink {
124
+ readonly version: number;
125
+ readonly sender: string;
126
+ readonly giftKey: string;
127
+ readonly kind: GiftKindName;
128
+ /** XNT: lamports paid out. Name: the escrowed price snapshot. */
129
+ readonly amount: bigint;
130
+ /** Name gifts only; "" for XNT. */
131
+ readonly name: string;
132
+ readonly handleType: number;
133
+ readonly integrator: string | null;
134
+ readonly integratorRateBps: number;
135
+ readonly expiresAt: bigint;
136
+ readonly createdAt: bigint;
137
+ readonly bump: number;
138
+ }
139
+ /**
140
+ * Decode a raw `GiftLink` (discriminator included) in the Borsh order of
141
+ * state.rs: version u8 | sender | gift_key | kind u8 | amount u64 |
142
+ * name [u8;32] | name_len u8 | handle_type u8 | integrator Option<Pubkey> |
143
+ * integrator_rate_bps u16 | expires_at i64 | created_at i64 | bump u8.
144
+ * Null when the bytes are not a `GiftLink`.
145
+ */
146
+ export declare function decodeGiftLink(data: Uint8Array): GiftLink | null;
147
+ /** Fetch + decode a gift link by its escrow address (null if absent: never
148
+ * created, or already claimed / refunded). */
149
+ export declare function fetchGiftLink(rpc: RpcFn, address: AddressLike, opts?: {
150
+ readonly programId?: AddressLike;
151
+ readonly commitment?: string;
152
+ }): Promise<{
153
+ readonly address: string;
154
+ readonly gift: GiftLink;
155
+ readonly lamports: bigint;
156
+ } | null>;
157
+ /** `now >= expiresAt`: claims fail, anyone may refund to the sender. */
158
+ export declare function isGiftLinkExpired(g: Pick<GiftLink, "expiresAt">, nowSecs: bigint | number): boolean;
159
+ /** The Borsh arguments of `create_gift_link` (after the discriminator):
160
+ * `kind u8, amount u64, name String, handle_type u8, expires_at i64`. */
161
+ export declare function encodeCreateGiftLinkArgs(a: {
162
+ readonly kind: GiftKindName;
163
+ readonly amount: bigint;
164
+ readonly name: string;
165
+ readonly handleType: number;
166
+ readonly expiresAt: bigint;
167
+ }): Uint8Array;
168
+ interface CreateGiftLinkBase {
169
+ readonly programId: AddressLike;
170
+ /** Signer; funds the escrow + rent and receives refunds. */
171
+ readonly sender: AddressLike;
172
+ /** Signer; the ephemeral gift key's PUBLIC key ({@link newGiftLinkKey}). */
173
+ readonly giftKey: AddressLike;
174
+ /** `["config"]`. */
175
+ readonly config: AddressLike;
176
+ /** {@link findGiftLinkPda}(giftKey). */
177
+ readonly gift: AddressLike;
178
+ /** Unix seconds; in the future and at most {@link MAX_GIFT_LINK_DURATION_SECS} ahead. */
179
+ readonly expiresAt: bigint;
180
+ }
181
+ export interface CreateXntGiftLinkParams extends CreateGiftLinkBase {
182
+ readonly kind: "xnt";
183
+ /** Lamports; at least the rent-exempt minimum of an empty account
184
+ * (890 880 on X1), else `GiftAmountInvalid` (6151). */
185
+ readonly amount: bigint;
186
+ }
187
+ export interface CreateNameGiftLinkParams extends CreateGiftLinkBase {
188
+ readonly kind: "name";
189
+ /** Canonical bare name (no `@`), unregistered, no pending voucher. */
190
+ readonly name: string;
191
+ readonly handleType: HandleTypeValue;
192
+ /** `["handle", name]`. */
193
+ readonly handle: AddressLike;
194
+ /** `["voucher", name]`. */
195
+ readonly voucher: AddressLike;
196
+ /** Optional integrator pair (integrator.ts `integratorAccountsForCreateVoucher`). */
197
+ readonly integratorAccounts?: readonly InstructionKey[];
198
+ }
199
+ /**
200
+ * `create_gift_link(kind, amount, name, handle_type, expires_at)`. Accounts
201
+ * (`CreateGiftLink`): sender(s,w), gift_key(s), config, gift(w),
202
+ * system_program, then (name gifts) handle, voucher and the optional
203
+ * integrator pair. A name gift's escrow is priced by the program (the live
204
+ * price snapshot); `amount` is sent as 0.
205
+ */
206
+ export declare function buildCreateGiftLinkIx(p: CreateXntGiftLinkParams | CreateNameGiftLinkParams): BuiltInstruction;
207
+ export interface ClaimGiftLinkParams {
208
+ readonly programId: AddressLike;
209
+ /** Signer: the gift key from the link. */
210
+ readonly giftKey: AddressLike;
211
+ /** `["config"]`. */
212
+ readonly config: AddressLike;
213
+ /** {@link findGiftLinkPda}(giftKey). */
214
+ readonly gift: AddressLike;
215
+ /** `GiftLink.sender` (receives the escrow's rent). */
216
+ readonly sender: AddressLike;
217
+ /** The recipient's wallet: gets exactly `GiftLink.amount` lamports. */
218
+ readonly destination: AddressLike;
219
+ }
220
+ /** `claim_gift_link()` (XNT gifts). Accounts (`ClaimGiftLink`): gift_key(s),
221
+ * config, gift(w), sender(w), destination(w). The fee payer is whoever
222
+ * submits (not an account of this instruction). */
223
+ export declare function buildClaimGiftLinkIx(p: ClaimGiftLinkParams): BuiltInstruction;
224
+ export interface ClaimGiftLinkNameParams extends TokenizedTailAccounts {
225
+ readonly programId: AddressLike;
226
+ /** Signer; fronts the tx fee and every new account's rent (the rent is
227
+ * reimbursed from the escrow). May be a relayer. */
228
+ readonly claimer: AddressLike;
229
+ /** Signer: the gift key from the link. */
230
+ readonly giftKey: AddressLike;
231
+ /** `["config"]`. */
232
+ readonly config: AddressLike;
233
+ /** `Config.treasury`. */
234
+ readonly treasury: AddressLike;
235
+ /** {@link findGiftLinkPda}(giftKey). */
236
+ readonly gift: AddressLike;
237
+ /** `GiftLink.sender`. */
238
+ readonly sender: AddressLike;
239
+ /** The destination wallet: owns the name and its pNFT. The tail's
240
+ * `ownerTokenAccount` / `tokenRecord` derive against THIS address
241
+ * (`deriveTokenizedNftAccounts(find, { programId, owner, handle })`). */
242
+ readonly owner: AddressLike;
243
+ /** `["handle", name]`. */
244
+ readonly handle: AddressLike;
245
+ /** `GiftLink.name`. */
246
+ readonly name: string;
247
+ /** REQUIRED iff `GiftLink.integrator` is set (must equal it). */
248
+ readonly integrator?: AddressLike;
249
+ }
250
+ /**
251
+ * `claim_gift_link_name(name)` (name gifts): registers the name to `owner` as
252
+ * a tokenized pNFT, exactly like `claim_voucher_tokenized`. Accounts
253
+ * (`ClaimGiftLinkName`): claimer(s,w), gift_key(s), config(w), treasury(w),
254
+ * gift(w), sender(w), owner, handle(w), then the 14-account pNFT tail, then
255
+ * the optional integrator(w). Prepend a ComputeBudget
256
+ * `SetComputeUnitLimit(CLAIM_GIFT_LINK_NAME_CU_LIMIT)`.
257
+ */
258
+ export declare function buildClaimGiftLinkNameIx(p: ClaimGiftLinkNameParams): BuiltInstruction;
259
+ export interface RefundGiftLinkParams {
260
+ readonly programId: AddressLike;
261
+ /** Signer: the sender (any time) or anyone (at/after `expiresAt`). */
262
+ readonly caller: AddressLike;
263
+ readonly gift: AddressLike;
264
+ /** `GiftLink.sender`: receives escrow + rent. */
265
+ readonly sender: AddressLike;
266
+ }
267
+ /** `refund_gift_link()`. Accounts (`RefundGiftLink`): caller(s), gift(w),
268
+ * sender(w). */
269
+ export declare function buildRefundGiftLinkIx(p: RefundGiftLinkParams): BuiltInstruction;
270
+ /** Plain-language sentence for a gift-link error code (6151-6156, 6231), or null. */
271
+ export declare function giftLinkErrorMessage(code: number): string | null;
272
+ export {};