@x1id/resolve 0.23.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 (49) 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 +13 -0
  20. package/dist/index.js +32 -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/nsGate.d.ts +98 -0
  28. package/dist/nsGate.js +172 -0
  29. package/dist/oidc.d.ts +272 -0
  30. package/dist/oidc.js +641 -0
  31. package/dist/pay.d.ts +513 -0
  32. package/dist/pay.js +756 -0
  33. package/dist/payment.d.ts +9 -2
  34. package/dist/payment.js +9 -2
  35. package/dist/recovery.d.ts +186 -0
  36. package/dist/recovery.js +276 -0
  37. package/dist/recoveryNft.d.ts +183 -0
  38. package/dist/recoveryNft.js +291 -0
  39. package/dist/shield.d.ts +373 -0
  40. package/dist/shield.js +695 -0
  41. package/dist/subscription.d.ts +439 -0
  42. package/dist/subscription.js +642 -0
  43. package/dist/textRecords.d.ts +52 -6
  44. package/dist/textRecords.js +80 -8
  45. package/dist/types.d.ts +3 -0
  46. package/dist/x402Receipt.d.ts +246 -0
  47. package/dist/x402Receipt.js +374 -0
  48. package/package.json +18 -1
  49. package/schema/agent-manifest.json +91 -8
@@ -0,0 +1,669 @@
1
+ /**
2
+ * X1ID Verify: third-party credentials (docs/specs/x1id-credentials.md,
3
+ * program upgrade 6). Instruction builders, account decoders, PDA finders and
4
+ * the off-chain validity rule for issuer-signed credentials on `@names`.
5
+ *
6
+ * # The model
7
+ *
8
+ * - Anyone registers an **issuer** (`["issuer", authority]`) for a small fee
9
+ * paid to the X1ID treasury. The authority KEY is the issuer's identity; the
10
+ * display name is a label and must never be trusted. An issuer can prove it
11
+ * is `@acme` with {@link buildLinkIssuerHandleIx} (the name's holder
12
+ * co-signs); {@link issuerLinkValid} checks the link is still current.
13
+ * - Issuers define immutable **schemas** (`["cred_schema", issuer, id]`).
14
+ * - A **credential** (`["credential", issuer, handle, id]`) is issued with the
15
+ * name's CURRENT holder co-signing, may expire, and is revoked only by its
16
+ * issuer. Anyone may garbage-collect a dead one (rent goes back to whoever
17
+ * paid it).
18
+ * - A credential counts for the current holder only while it is unexpired,
19
+ * its issuer is not Retired and has not invalidated it, it was issued in
20
+ * the name's current ownership epoch, and its `subject` IS the current
21
+ * holder ({@link credentialValidity}).
22
+ *
23
+ * # Never a blue tick
24
+ *
25
+ * Third-party credentials NEVER satisfy the X1ID blue tick: `isHandleVerified`,
26
+ * `"any-verified"` and `{ kind }` gates read only X1ID `Attestation` accounts.
27
+ * Gate on a credential with an explicit `{ credential: { schema, issuers } }`
28
+ * clause (gate.ts).
29
+ *
30
+ * # Integrators: the issuer set is YOURS
31
+ *
32
+ * **When you call `assert_credential` on-chain (CPI or sibling instruction),
33
+ * the accepted issuer set must be a constant of your program or config,
34
+ * never taken from the end user's input.** Otherwise a user registers an
35
+ * issuer, issues a credential to themselves, and names it.
36
+ *
37
+ * Routed through the program's `fallback` (`extra_ix`): `sha256("global:<name>")
38
+ * [..8]` + raw Borsh, accounts exactly in the order of the program's
39
+ * `Accounts` structs (programs/x1-handles/src/credentials.rs). Hand-rolled,
40
+ * no web3.js.
41
+ */
42
+ import { encodeBase58, decodeBase58_32 } from "./base58.js";
43
+ // ---------------------------------------------------------------------------
44
+ // constants (mirror programs/x1-handles/src/credentials.rs)
45
+ // ---------------------------------------------------------------------------
46
+ export const ISSUER_SEED = "issuer";
47
+ export const CRED_SCHEMA_SEED = "cred_schema";
48
+ export const CREDENTIAL_SEED = "credential";
49
+ export const CRED_CONFIG_SEED = "cred_config";
50
+ export const RECOMMENDED_ISSUERS_SEED = "recommended_issuers";
51
+ /** Allocated sizes (8-byte discriminator + `INIT_SPACE`). */
52
+ export const ISSUER_ACCOUNT_LEN = 8 + 416;
53
+ export const CREDENTIAL_SCHEMA_ACCOUNT_LEN = 8 + 324;
54
+ export const CREDENTIAL_ACCOUNT_LEN = 8 + 214;
55
+ export const CRED_CONFIG_ACCOUNT_LEN = 8 + 26;
56
+ export const RECOMMENDED_ISSUERS_ACCOUNT_LEN = 8 + 1038;
57
+ /** Registration fee while the admin never set one: 1 XNT. */
58
+ export const DEFAULT_ISSUER_FEE_LAMPORTS = 1000000000n;
59
+ /** Program caps on the admin-set fees: 100 XNT and 0.1 XNT. */
60
+ export const ISSUER_FEE_MAX_LAMPORTS = 100000000000n;
61
+ export const CREDENTIAL_FEE_MAX_LAMPORTS = 100000000n;
62
+ /** No credential may be valid for more than 10 years (3653 days). */
63
+ export const MAX_CREDENTIAL_VALIDITY_SECS = 3653n * 86400n;
64
+ export const CRED_URI_MAX_LEN = 200;
65
+ /** `assert_credential` accepts 1..=8 issuers. */
66
+ export const MAX_ACCEPTED_ISSUERS = 8;
67
+ export const MAX_RECOMMENDED_ISSUERS = 32;
68
+ export const ISSUER_STATUS = { Active: 0, Paused: 1, Retired: 2 };
69
+ export const SCHEMA_STATUS = { Active: 0, Deprecated: 1 };
70
+ /** Compute-unit limits to request (measured in tests/credentials.rs, with
71
+ * headroom; spec §12). */
72
+ export const CREDENTIAL_CU_LIMITS = {
73
+ registerIssuer: 60_000,
74
+ createSchema: 50_000,
75
+ issueCredential: 100_000,
76
+ renewCredential: 80_000,
77
+ revokeCredential: 50_000,
78
+ closeCredential: 50_000,
79
+ assertCredential: 80_000,
80
+ };
81
+ const d8 = (...b) => Uint8Array.from(b);
82
+ export const REGISTER_ISSUER_DISCRIMINATOR = d8(145, 117, 52, 59, 189, 27, 127, 18);
83
+ export const UPDATE_ISSUER_DISCRIMINATOR = d8(9, 100, 234, 30, 84, 43, 30, 29);
84
+ export const LINK_ISSUER_HANDLE_DISCRIMINATOR = d8(23, 23, 31, 237, 147, 43, 237, 96);
85
+ export const UNLINK_ISSUER_HANDLE_DISCRIMINATOR = d8(5, 206, 46, 125, 153, 25, 244, 141);
86
+ export const CREATE_SCHEMA_DISCRIMINATOR = d8(105, 171, 40, 140, 30, 91, 30, 134);
87
+ export const DEPRECATE_SCHEMA_DISCRIMINATOR = d8(17, 211, 95, 93, 93, 28, 134, 203);
88
+ export const ISSUE_CREDENTIAL_DISCRIMINATOR = d8(255, 193, 171, 224, 68, 171, 194, 87);
89
+ export const RENEW_CREDENTIAL_DISCRIMINATOR = d8(181, 54, 171, 106, 20, 185, 136, 131);
90
+ export const REVOKE_CREDENTIAL_DISCRIMINATOR = d8(38, 123, 95, 95, 223, 158, 169, 87);
91
+ export const CLOSE_CREDENTIAL_DISCRIMINATOR = d8(213, 210, 242, 210, 169, 79, 220, 112);
92
+ export const ASSERT_CREDENTIAL_DISCRIMINATOR = d8(8, 158, 83, 118, 209, 229, 25, 215);
93
+ export const SET_CREDENTIAL_CONFIG_DISCRIMINATOR = d8(35, 210, 7, 175, 5, 130, 150, 109);
94
+ export const SET_RECOMMENDED_ISSUERS_DISCRIMINATOR = d8(163, 195, 233, 222, 182, 84, 219, 150);
95
+ /** `sha256("account:<Name>")[..8]`. */
96
+ export const ISSUER_ACCOUNT_DISCRIMINATOR = d8(216, 19, 83, 230, 108, 53, 80, 14);
97
+ export const CREDENTIAL_SCHEMA_ACCOUNT_DISCRIMINATOR = d8(175, 122, 25, 51, 201, 54, 253, 222);
98
+ export const CREDENTIAL_ACCOUNT_DISCRIMINATOR = d8(145, 44, 68, 220, 67, 46, 100, 135);
99
+ export const CRED_CONFIG_ACCOUNT_DISCRIMINATOR = d8(120, 101, 56, 66, 129, 114, 34, 87);
100
+ export const RECOMMENDED_ISSUERS_ACCOUNT_DISCRIMINATOR = d8(146, 35, 166, 67, 212, 149, 13, 177);
101
+ /** Byte offsets for `getProgramAccounts` `memcmp` filters. */
102
+ export const ISSUER_OFFSETS = { authority: 9, status: 41, invalidateBefore: 42, name: 50, linkedHandle: 115, linkedBy: 147 };
103
+ export const CREDENTIAL_SCHEMA_OFFSETS = { issuer: 9, schemaId: 41, status: 45 };
104
+ export const CREDENTIAL_OFFSETS = { issuer: 9, handle: 41, schemaId: 73, subject: 77, rentPayer: 189 };
105
+ /** Program error codes of the credentials block (fixed range 6158-6189;
106
+ * 6181-6189 are reserved placeholders). */
107
+ export const CREDENTIAL_ERRORS = {
108
+ IssuerNameInvalid: 6158,
109
+ IssuerNameReserved: 6159,
110
+ IssuerUriTooLong: 6160,
111
+ IssuerNotActive: 6161,
112
+ IssuerRetired: 6162,
113
+ NotIssuerAuthority: 6163,
114
+ InvalidateBeforeInvalid: 6164,
115
+ IssuerStatusInvalid: 6165,
116
+ SchemaNameInvalid: 6166,
117
+ SchemaNotActive: 6167,
118
+ SchemaIssuerMismatch: 6168,
119
+ SchemaValidityInvalid: 6169,
120
+ CredentialExpiryInvalid: 6170,
121
+ CredentialExpiryRequired: 6171,
122
+ CredentialSubjectNotAuthority: 6172,
123
+ CredentialNotRenewable: 6173,
124
+ CredentialStillValid: 6174,
125
+ CredentialIssuerSetInvalid: 6175,
126
+ CredentialGateNotSatisfied: 6176,
127
+ CredentialFeeAboveCap: 6177,
128
+ RecommendedListInvalid: 6178,
129
+ IssuerHandleLinkInvalid: 6179,
130
+ CredConfigMismatch: 6180,
131
+ };
132
+ const CREDENTIAL_ERROR_MESSAGES = {
133
+ 6158: "issuer name must be 3-32 characters of a-z, 0-9, space, '.', '_' or '-'",
134
+ 6159: "issuer names may not contain 'x1id'",
135
+ 6160: "uri is longer than 200 bytes",
136
+ 6161: "issuer is not active",
137
+ 6162: "issuer is retired",
138
+ 6163: "signer is not this issuer's authority",
139
+ 6164: "invalidate_before must not decrease and must not be in the future",
140
+ 6165: "invalid issuer status",
141
+ 6166: "schema name must be 3-32 characters of a-z, 0-9, '.', '_' or '-'",
142
+ 6167: "credential schema is not active",
143
+ 6168: "credential schema does not belong to this issuer",
144
+ 6169: "schema max validity must be between 0 and 10 years",
145
+ 6170: "credential expiry is invalid",
146
+ 6171: "this schema requires credentials to expire",
147
+ 6172: "credential subject is not the name's current authority",
148
+ 6173: "credential is not valid and cannot be renewed",
149
+ 6174: "credential may still be valid; only its issuer can revoke it",
150
+ 6175: "accepted issuer set must have 1 to 8 entries",
151
+ 6176: "credential gate not satisfied",
152
+ 6177: "credential fee is above the program cap",
153
+ 6178: "recommended issuer list is invalid",
154
+ 6179: "handle link is invalid: the name's current authority must co-sign",
155
+ 6180: "cred_config account mismatch",
156
+ };
157
+ /** Human message for a credentials error code, or null for another code. */
158
+ export function credentialErrorMessage(code) {
159
+ return CREDENTIAL_ERROR_MESSAGES[code] ?? null;
160
+ }
161
+ const SYSTEM_PROGRAM = "11111111111111111111111111111111";
162
+ const TE = new TextEncoder();
163
+ const TD = new TextDecoder();
164
+ const U32_MAX = 0xffff_ffff;
165
+ const U64_MAX = 0xffffffffffffffffn;
166
+ const I64_MIN = -(1n << 63n);
167
+ const I64_MAX = (1n << 63n) - 1n;
168
+ // ---------------------------------------------------------------------------
169
+ // encoding helpers
170
+ // ---------------------------------------------------------------------------
171
+ function toBytes32(v, what) {
172
+ if (typeof v === "string") {
173
+ const b = decodeBase58_32(v);
174
+ if (!b)
175
+ throw new Error(`${what} is not a valid base58 address`);
176
+ return b;
177
+ }
178
+ if (v.length !== 32)
179
+ throw new Error(`${what} must be exactly 32 bytes`);
180
+ return v;
181
+ }
182
+ function toBase58(v, what) {
183
+ return encodeBase58(toBytes32(v, what));
184
+ }
185
+ function key(pubkey, what, isSigner, isWritable) {
186
+ return { pubkey: toBase58(pubkey, what), isSigner, isWritable };
187
+ }
188
+ function concat(...parts) {
189
+ const out = new Uint8Array(parts.reduce((a, p) => a + p.length, 0));
190
+ let o = 0;
191
+ for (const p of parts) {
192
+ out.set(p, o);
193
+ o += p.length;
194
+ }
195
+ return out;
196
+ }
197
+ function u32le(v, what) {
198
+ if (!Number.isInteger(v) || v < 0 || v > U32_MAX)
199
+ throw new Error(`${what} must be a u32`);
200
+ const out = new Uint8Array(4);
201
+ new DataView(out.buffer).setUint32(0, v, true);
202
+ return out;
203
+ }
204
+ function u64le(v, what) {
205
+ if (typeof v !== "bigint" || v < 0n || v > U64_MAX)
206
+ throw new Error(`${what} must be a u64 bigint`);
207
+ const out = new Uint8Array(8);
208
+ new DataView(out.buffer).setBigUint64(0, v, true);
209
+ return out;
210
+ }
211
+ function i64le(v, what) {
212
+ if (typeof v !== "bigint" || v < I64_MIN || v > I64_MAX)
213
+ throw new Error(`${what} must be an i64 bigint`);
214
+ const out = new Uint8Array(8);
215
+ new DataView(out.buffer).setBigInt64(0, v, true);
216
+ return out;
217
+ }
218
+ function encString(s) {
219
+ const bytes = TE.encode(s);
220
+ return concat(u32le(bytes.length, "string length"), bytes);
221
+ }
222
+ function hash32(v, what) {
223
+ if (v === undefined)
224
+ return new Uint8Array(32);
225
+ if (v.length !== 32)
226
+ throw new Error(`${what} must be 32 bytes`);
227
+ return v;
228
+ }
229
+ function opt(v) {
230
+ return v === undefined ? Uint8Array.of(0) : concat(Uint8Array.of(1), v);
231
+ }
232
+ function tailKeys(p) {
233
+ const out = [];
234
+ if (p.holderAta !== undefined)
235
+ out.push(key(p.holderAta, "holderAta", false, false));
236
+ if (p.namespace !== undefined)
237
+ out.push(key(p.namespace, "namespace", false, false));
238
+ return out;
239
+ }
240
+ function ix(programId, keys, data) {
241
+ return { programId: toBase58(programId, "programId"), keys, data };
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // name rules (mirror the program; the program is the authority)
245
+ // ---------------------------------------------------------------------------
246
+ /** The display-name rule: 3-32 bytes of `[a-z0-9._-]` (plus single inner
247
+ * spaces for issuers), starting and ending with `[a-z0-9]`. */
248
+ export function isValidCredentialName(name, allowSpace = true) {
249
+ if (!/^[a-z0-9][a-z0-9 ._-]{1,30}[a-z0-9]$/.test(name))
250
+ return false;
251
+ if (!allowSpace && name.includes(" "))
252
+ return false;
253
+ return !name.includes(" ");
254
+ }
255
+ /** Owner decision Q7: issuer names may not contain `x1id`, also split by
256
+ * separators. */
257
+ export function isReservedIssuerName(name) {
258
+ return name.replace(/[^a-z0-9]/gi, "").toLowerCase().includes("x1id");
259
+ }
260
+ // ---------------------------------------------------------------------------
261
+ // PDAs
262
+ // ---------------------------------------------------------------------------
263
+ function idSeed(schemaId) {
264
+ return u32le(schemaId, "schemaId");
265
+ }
266
+ /** `["issuer", authority]`. */
267
+ export async function findIssuerPda(find, programId, authority) {
268
+ return find([TE.encode(ISSUER_SEED), toBytes32(authority, "authority")], toBase58(programId, "programId"));
269
+ }
270
+ /** `["cred_schema", issuer, schema_id u32 LE]`. */
271
+ export async function findCredentialSchemaPda(find, programId, issuer, schemaId) {
272
+ return find([TE.encode(CRED_SCHEMA_SEED), toBytes32(issuer, "issuer"), idSeed(schemaId)], toBase58(programId, "programId"));
273
+ }
274
+ /** `["credential", issuer, handle, schema_id u32 LE]`. */
275
+ export async function findCredentialPda(find, programId, issuer, handle, schemaId) {
276
+ return find([TE.encode(CREDENTIAL_SEED), toBytes32(issuer, "issuer"), toBytes32(handle, "handle"), idSeed(schemaId)], toBase58(programId, "programId"));
277
+ }
278
+ /** `["cred_config"]`. */
279
+ export async function findCredConfigPda(find, programId) {
280
+ return find([TE.encode(CRED_CONFIG_SEED)], toBase58(programId, "programId"));
281
+ }
282
+ /** `["recommended_issuers"]`. */
283
+ export async function findRecommendedIssuersPda(find, programId) {
284
+ return find([TE.encode(RECOMMENDED_ISSUERS_SEED)], toBase58(programId, "programId"));
285
+ }
286
+ // ---------------------------------------------------------------------------
287
+ // decoders
288
+ // ---------------------------------------------------------------------------
289
+ function hasDisc(data, disc) {
290
+ if (data.length < 8)
291
+ return false;
292
+ for (let i = 0; i < 8; i++)
293
+ if (data[i] !== disc[i])
294
+ return false;
295
+ return true;
296
+ }
297
+ function view(data) {
298
+ return new DataView(data.buffer, data.byteOffset, data.byteLength);
299
+ }
300
+ const b58 = (d, o) => encodeBase58(d.slice(o, o + 32));
301
+ const ZERO_KEY = encodeBase58(new Uint8Array(32));
302
+ /** Decode a raw `Issuer` (discriminator included); null if it is not one. */
303
+ export function decodeIssuer(data) {
304
+ if (data.length < 8 + 220 + 4 || !hasDisc(data, ISSUER_ACCOUNT_DISCRIMINATOR))
305
+ return null;
306
+ const v = view(data);
307
+ const status = data[41];
308
+ if (status > 2)
309
+ return null;
310
+ const nameLen = data[82];
311
+ if (nameLen > 32)
312
+ return null;
313
+ const uriLen = v.getUint32(220, true);
314
+ if (uriLen > CRED_URI_MAX_LEN || data.length < 224 + uriLen)
315
+ return null;
316
+ const linked = b58(data, 115);
317
+ return {
318
+ version: data[8],
319
+ authority: b58(data, 9),
320
+ status: ["active", "paused", "retired"][status],
321
+ invalidateBefore: v.getBigInt64(42, true),
322
+ name: TD.decode(data.slice(50, 50 + nameLen)),
323
+ uriHash: data.slice(83, 115),
324
+ linkedHandle: linked === ZERO_KEY ? null : linked,
325
+ linkedBy: linked === ZERO_KEY ? null : b58(data, 147),
326
+ linkedAt: v.getBigInt64(179, true),
327
+ createdAt: v.getBigInt64(187, true),
328
+ feePaid: v.getBigUint64(195, true),
329
+ issuedCount: v.getBigUint64(203, true),
330
+ revokedCount: v.getBigUint64(211, true),
331
+ bump: data[219],
332
+ uri: TD.decode(data.slice(224, 224 + uriLen)),
333
+ };
334
+ }
335
+ export function decodeCredentialSchema(data) {
336
+ if (data.length < 8 + 128 + 4 || !hasDisc(data, CREDENTIAL_SCHEMA_ACCOUNT_DISCRIMINATOR))
337
+ return null;
338
+ const v = view(data);
339
+ const status = data[45];
340
+ if (status > 1)
341
+ return null;
342
+ const nameLen = data[86];
343
+ if (nameLen > 32)
344
+ return null;
345
+ const uriLen = v.getUint32(128, true);
346
+ if (uriLen > CRED_URI_MAX_LEN || data.length < 132 + uriLen)
347
+ return null;
348
+ return {
349
+ version: data[8],
350
+ issuer: b58(data, 9),
351
+ schemaId: v.getUint32(41, true),
352
+ status: status === 0 ? "active" : "deprecated",
353
+ maxValiditySecs: v.getBigInt64(46, true),
354
+ name: TD.decode(data.slice(54, 54 + nameLen)),
355
+ contentHash: data.slice(87, 119),
356
+ createdAt: v.getBigInt64(119, true),
357
+ bump: data[127],
358
+ uri: TD.decode(data.slice(132, 132 + uriLen)),
359
+ };
360
+ }
361
+ export function decodeCredential(data) {
362
+ if (data.length < CREDENTIAL_ACCOUNT_LEN || !hasDisc(data, CREDENTIAL_ACCOUNT_DISCRIMINATOR))
363
+ return null;
364
+ const v = view(data);
365
+ return {
366
+ version: data[8],
367
+ issuer: b58(data, 9),
368
+ handle: b58(data, 41),
369
+ schemaId: v.getUint32(73, true),
370
+ subject: b58(data, 77),
371
+ evidenceHash: data.slice(109, 141),
372
+ claimHash: data.slice(141, 173),
373
+ issuedAt: v.getBigInt64(173, true),
374
+ expiresAt: v.getBigInt64(181, true),
375
+ rentPayer: b58(data, 189),
376
+ bump: data[221],
377
+ };
378
+ }
379
+ export function decodeCredConfig(data) {
380
+ if (data.length < CRED_CONFIG_ACCOUNT_LEN || !hasDisc(data, CRED_CONFIG_ACCOUNT_DISCRIMINATOR))
381
+ return null;
382
+ const v = view(data);
383
+ return {
384
+ issuerFeeLamports: v.getBigUint64(9, true),
385
+ credentialFeeLamports: v.getBigUint64(17, true),
386
+ updatedAt: v.getBigInt64(25, true),
387
+ };
388
+ }
389
+ export function decodeRecommendedIssuers(data) {
390
+ if (data.length < 8 + 1 + 4 || !hasDisc(data, RECOMMENDED_ISSUERS_ACCOUNT_DISCRIMINATOR))
391
+ return null;
392
+ const v = view(data);
393
+ const n = v.getUint32(9, true);
394
+ if (n > MAX_RECOMMENDED_ISSUERS || data.length < 13 + 32 * n + 8)
395
+ return null;
396
+ const issuers = [];
397
+ for (let i = 0; i < n; i++)
398
+ issuers.push(b58(data, 13 + 32 * i));
399
+ return { issuers, updatedAt: v.getBigInt64(13 + 32 * n, true) };
400
+ }
401
+ /**
402
+ * Whether `cred` counts for the handle's CURRENT holder at `nowSecs` — the
403
+ * same checks `assert_credential` makes. `records_cleared_at` deliberately
404
+ * plays no part (owner decision Q6). Pass `acceptedIssuers` / `schemaId` /
405
+ * `minRemainingSecs` to apply a gate's terms as well.
406
+ */
407
+ export function credentialValidity(cred, issuer, ctx, nowSecs, terms = {}) {
408
+ const now = BigInt(nowSecs);
409
+ const reasons = [];
410
+ if (ctx.handle === null)
411
+ reasons.push("released");
412
+ else if (ctx.handle !== cred.handle)
413
+ reasons.push("wrong_handle");
414
+ if (terms.schemaId !== undefined && terms.schemaId !== cred.schemaId)
415
+ reasons.push("wrong_schema");
416
+ if (terms.acceptedIssuers !== undefined && !terms.acceptedIssuers.includes(cred.issuer))
417
+ reasons.push("issuer_not_accepted");
418
+ const min = BigInt(terms.minRemainingSecs ?? 0);
419
+ if (cred.expiresAt !== 0n) {
420
+ if (now >= cred.expiresAt)
421
+ reasons.push("expired");
422
+ else if (min < 0n || now + min >= cred.expiresAt)
423
+ reasons.push("insufficient_headroom");
424
+ }
425
+ else if (min < 0n)
426
+ reasons.push("insufficient_headroom");
427
+ if (issuer.status === "retired")
428
+ reasons.push("issuer_retired");
429
+ if (cred.issuedAt < issuer.invalidateBefore)
430
+ reasons.push("invalidated");
431
+ if (ctx.handle !== null && !(cred.issuedAt >= ctx.registeredAt && (ctx.holderEpoch === 0n || cred.issuedAt > ctx.holderEpoch))) {
432
+ reasons.push("epoch");
433
+ }
434
+ if (ctx.currentHolder === null)
435
+ reasons.push("holder_unknown");
436
+ else if (ctx.currentHolder !== cred.subject)
437
+ reasons.push("holder_mismatch");
438
+ return { valid: reasons.length === 0, reasons };
439
+ }
440
+ /**
441
+ * Whether an issuer's `linkedHandle` still proves "this issuer is @name":
442
+ * the link was made in the handle's current ownership epoch and the wallet
443
+ * that co-signed it is still the handle's current authority.
444
+ */
445
+ export function issuerLinkValid(issuer, ctx) {
446
+ if (!issuer.linkedHandle || ctx.handle !== issuer.linkedHandle)
447
+ return false;
448
+ if (!(issuer.linkedAt >= ctx.registeredAt && (ctx.holderEpoch === 0n || issuer.linkedAt > ctx.holderEpoch)))
449
+ return false;
450
+ return ctx.currentHolder !== null && ctx.currentHolder === issuer.linkedBy;
451
+ }
452
+ /** Decode the 52-byte `set_return_data` of a passing `assert_credential`. */
453
+ export function decodeAssertCredentialReturn(data) {
454
+ if (data.length !== 52)
455
+ return null;
456
+ const v = view(data);
457
+ return { issuer: b58(data, 0), schemaId: v.getUint32(32, true), issuedAt: v.getBigInt64(36, true), expiresAt: v.getBigInt64(44, true) };
458
+ }
459
+ // ---------------------------------------------------------------------------
460
+ // chain reads
461
+ // ---------------------------------------------------------------------------
462
+ function b64ToBytes(s) {
463
+ const bin = atob(s);
464
+ const out = new Uint8Array(bin.length);
465
+ for (let i = 0; i < bin.length; i++)
466
+ out[i] = bin.charCodeAt(i);
467
+ return out;
468
+ }
469
+ /** Fetch raw data for several accounts in one `getMultipleAccounts` (null
470
+ * for a missing account, or one not owned by `programId`). */
471
+ export async function fetchProgramAccountsData(rpc, programId, addresses, commitment = "confirmed") {
472
+ if (addresses.length === 0)
473
+ return [];
474
+ const owner = toBase58(programId, "programId");
475
+ const out = [];
476
+ for (let i = 0; i < addresses.length; i += 100) {
477
+ const chunk = addresses.slice(i, i + 100);
478
+ const res = (await rpc("getMultipleAccounts", [chunk, { encoding: "base64", commitment }]));
479
+ const vals = res?.value ?? [];
480
+ for (let j = 0; j < chunk.length; j++) {
481
+ const v = vals[j];
482
+ out.push(v && v.owner === owner ? b64ToBytes(v.data[0]) : null);
483
+ }
484
+ }
485
+ return out;
486
+ }
487
+ /** Read the advisory recommended-issuer list (empty if never written). */
488
+ export async function fetchRecommendedIssuers(rpc, find, programId) {
489
+ const addr = await findRecommendedIssuersPda(find, programId);
490
+ const [raw] = await fetchProgramAccountsData(rpc, programId, [addr]);
491
+ return raw ? (decodeRecommendedIssuers(raw)?.issuers ?? []) : [];
492
+ }
493
+ /**
494
+ * Fetch the credentials `handle` holds for `schemaId` from each of `issuers`
495
+ * (PDAs derived, one `getMultipleAccounts`), with each one's validity for
496
+ * `ctx` at `nowSecs`. Missing credentials are omitted.
497
+ */
498
+ export async function fetchHandleCredentials(rpc, find, programId, ctx, schemaId, issuers, nowSecs, minRemainingSecs = 0) {
499
+ const credAddrs = await Promise.all(issuers.map((i) => findCredentialPda(find, programId, i, ctx.handle, schemaId)));
500
+ const raws = await fetchProgramAccountsData(rpc, programId, [...credAddrs, ...issuers]);
501
+ const out = [];
502
+ for (let i = 0; i < issuers.length; i++) {
503
+ const raw = raws[i];
504
+ const credential = raw ? decodeCredential(raw) : null;
505
+ if (!credential)
506
+ continue;
507
+ const iraw = raws[issuers.length + i];
508
+ const issuer = iraw ? decodeIssuer(iraw) : null;
509
+ const validity = issuer
510
+ ? credentialValidity(credential, issuer, ctx, nowSecs, { acceptedIssuers: issuers, schemaId, minRemainingSecs })
511
+ : { valid: false, reasons: ["issuer_not_accepted"] };
512
+ out.push({ address: credAddrs[i], credential, issuer, validity });
513
+ }
514
+ return out;
515
+ }
516
+ /** `register_issuer(name, uri, uri_hash)`. */
517
+ export function buildRegisterIssuerIx(p) {
518
+ if (!isValidCredentialName(p.name, true))
519
+ throw new Error("invalid issuer name");
520
+ if (isReservedIssuerName(p.name))
521
+ throw new Error("issuer names may not contain 'x1id'");
522
+ if (TE.encode(p.uri).length > CRED_URI_MAX_LEN)
523
+ throw new Error("uri is longer than 200 bytes");
524
+ return ix(p.programId, [
525
+ key(p.payer, "payer", true, true),
526
+ key(p.authority, "authority", true, false),
527
+ key(p.config, "config", false, false),
528
+ key(p.credConfig, "credConfig", false, false),
529
+ key(p.treasury, "treasury", false, true),
530
+ key(p.issuer, "issuer", false, true),
531
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
532
+ ], concat(REGISTER_ISSUER_DISCRIMINATOR, encString(p.name), encString(p.uri), hash32(p.uriHash, "uriHash")));
533
+ }
534
+ /** `update_issuer(uri?, uri_hash?, status?, invalidate_before?)`. Available
535
+ * during a lockdown (retiring a compromised key always works). */
536
+ export function buildUpdateIssuerIx(p) {
537
+ if (p.uri !== undefined && TE.encode(p.uri).length > CRED_URI_MAX_LEN)
538
+ throw new Error("uri is longer than 200 bytes");
539
+ const status = p.status === undefined ? undefined : Uint8Array.of(ISSUER_STATUS[p.status === "active" ? "Active" : p.status === "paused" ? "Paused" : "Retired"]);
540
+ return ix(p.programId, [key(p.authority, "authority", true, false), key(p.issuer, "issuer", false, true)], concat(UPDATE_ISSUER_DISCRIMINATOR, opt(p.uri === undefined ? undefined : encString(p.uri)), opt(p.uriHash === undefined ? undefined : hash32(p.uriHash, "uriHash")), opt(status), opt(p.invalidateBefore === undefined ? undefined : i64le(p.invalidateBefore, "invalidateBefore"))));
541
+ }
542
+ /** `link_issuer_handle()`: the issuer proves it is `@name`. */
543
+ export function buildLinkIssuerHandleIx(p) {
544
+ return ix(p.programId, [
545
+ key(p.authority, "authority", true, false),
546
+ key(p.handleAuthority, "handleAuthority", true, false),
547
+ key(p.issuer, "issuer", false, true),
548
+ key(p.handle, "handle", false, false),
549
+ ...tailKeys(p),
550
+ ], LINK_ISSUER_HANDLE_DISCRIMINATOR.slice());
551
+ }
552
+ /** `unlink_issuer_handle()`. */
553
+ export function buildUnlinkIssuerHandleIx(p) {
554
+ return ix(p.programId, [key(p.authority, "authority", true, false), key(p.issuer, "issuer", false, true)], UNLINK_ISSUER_HANDLE_DISCRIMINATOR.slice());
555
+ }
556
+ /** `create_schema(schema_id, name, uri, content_hash, max_validity_secs)`. */
557
+ export function buildCreateSchemaIx(p) {
558
+ if (!isValidCredentialName(p.name, false))
559
+ throw new Error("invalid schema name");
560
+ const uri = p.uri ?? "";
561
+ if (TE.encode(uri).length > CRED_URI_MAX_LEN)
562
+ throw new Error("uri is longer than 200 bytes");
563
+ if (p.maxValiditySecs < 0n || p.maxValiditySecs > MAX_CREDENTIAL_VALIDITY_SECS)
564
+ throw new Error("maxValiditySecs must be 0..10 years");
565
+ return ix(p.programId, [
566
+ key(p.payer, "payer", true, true),
567
+ key(p.authority, "authority", true, false),
568
+ key(p.config, "config", false, false),
569
+ key(p.issuer, "issuer", false, false),
570
+ key(p.schema, "schema", false, true),
571
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
572
+ ], concat(CREATE_SCHEMA_DISCRIMINATOR, u32le(p.schemaId, "schemaId"), encString(p.name), encString(uri), hash32(p.contentHash, "contentHash"), i64le(p.maxValiditySecs, "maxValiditySecs")));
573
+ }
574
+ /** `deprecate_schema(schema_id)`: Active -> Deprecated, one way. */
575
+ export function buildDeprecateSchemaIx(p) {
576
+ return ix(p.programId, [key(p.authority, "authority", true, false), key(p.issuer, "issuer", false, false), key(p.schema, "schema", false, true)], concat(DEPRECATE_SCHEMA_DISCRIMINATOR, u32le(p.schemaId, "schemaId")));
577
+ }
578
+ /** `issue_credential(schema_id, evidence_hash, claim_hash, expires_at)` —
579
+ * a three-signer transaction (payer, issuer authority, holder). */
580
+ export function buildIssueCredentialIx(p) {
581
+ return ix(p.programId, [
582
+ key(p.payer, "payer", true, true),
583
+ key(p.authority, "authority", true, false),
584
+ key(p.subject, "subject", true, false),
585
+ key(p.config, "config", false, false),
586
+ key(p.credConfig, "credConfig", false, false),
587
+ key(p.treasury, "treasury", false, true),
588
+ key(p.issuer, "issuer", false, true),
589
+ key(p.schema, "schema", false, false),
590
+ key(p.handle, "handle", false, false),
591
+ key(p.credential, "credential", false, true),
592
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
593
+ ...tailKeys(p),
594
+ ], concat(ISSUE_CREDENTIAL_DISCRIMINATOR, u32le(p.schemaId, "schemaId"), hash32(p.evidenceHash, "evidenceHash"), hash32(p.claimHash, "claimHash"), i64le(p.expiresAt, "expiresAt")));
595
+ }
596
+ /** `renew_credential(expires_at, evidence_hash?)` — issuer only; only while
597
+ * the credential still counts for its subject. */
598
+ export function buildRenewCredentialIx(p) {
599
+ return ix(p.programId, [
600
+ key(p.authority, "authority", true, false),
601
+ key(p.config, "config", false, false),
602
+ key(p.issuer, "issuer", false, false),
603
+ key(p.handle, "handle", false, false),
604
+ key(p.credential, "credential", false, true),
605
+ key(p.schema, "schema", false, false),
606
+ ...tailKeys(p),
607
+ ], concat(RENEW_CREDENTIAL_DISCRIMINATOR, i64le(p.expiresAt, "expiresAt"), opt(p.evidenceHash === undefined ? undefined : hash32(p.evidenceHash, "evidenceHash"))));
608
+ }
609
+ /** `revoke_credential()` — issuer only; rent to `rentPayer`. */
610
+ export function buildRevokeCredentialIx(p) {
611
+ return ix(p.programId, [
612
+ key(p.authority, "authority", true, false),
613
+ key(p.issuer, "issuer", false, true),
614
+ key(p.credential, "credential", false, true),
615
+ key(p.rentPayer, "rentPayer", false, true),
616
+ ], REVOKE_CREDENTIAL_DISCRIMINATOR.slice());
617
+ }
618
+ /** `close_credential()` — anyone (no signer), for a credential that can never
619
+ * count again; rent to `rentPayer`, never to the caller. */
620
+ export function buildCloseCredentialIx(p) {
621
+ return ix(p.programId, [
622
+ key(p.credential, "credential", false, true),
623
+ key(p.rentPayer, "rentPayer", false, true),
624
+ key(p.issuer, "issuer", false, false),
625
+ key(p.handle, "handle", false, false),
626
+ ], CLOSE_CREDENTIAL_DISCRIMINATOR.slice());
627
+ }
628
+ /** `assert_credential(schema_id, accepted_issuers, min_remaining_secs)` —
629
+ * the side-effect-free gate; compose it before your gated instruction. */
630
+ export function buildAssertCredentialIx(p) {
631
+ if (p.acceptedIssuers.length < 1 || p.acceptedIssuers.length > MAX_ACCEPTED_ISSUERS) {
632
+ throw new Error("acceptedIssuers must have 1 to 8 entries");
633
+ }
634
+ const min = p.minRemainingSecs ?? 0n;
635
+ if (min < 0n)
636
+ throw new Error("minRemainingSecs must be >= 0");
637
+ return ix(p.programId, [
638
+ key(p.caller, "caller", true, false),
639
+ key(p.handle, "handle", false, false),
640
+ key(p.credential, "credential", false, false),
641
+ key(p.issuer, "issuer", false, false),
642
+ ...tailKeys(p),
643
+ ], concat(ASSERT_CREDENTIAL_DISCRIMINATOR, u32le(p.schemaId, "schemaId"), u32le(p.acceptedIssuers.length, "acceptedIssuers length"), ...p.acceptedIssuers.map((a, i) => toBytes32(a, `acceptedIssuers[${i}]`)), i64le(min, "minRemainingSecs")));
644
+ }
645
+ /** `set_credential_config(issuer_fee, credential_fee)` — `Config.admin`. */
646
+ export function buildSetCredentialConfigIx(p) {
647
+ if (p.issuerFeeLamports > ISSUER_FEE_MAX_LAMPORTS || p.credentialFeeLamports > CREDENTIAL_FEE_MAX_LAMPORTS) {
648
+ throw new Error("fee above the program cap");
649
+ }
650
+ return ix(p.programId, [
651
+ key(p.admin, "admin", true, true),
652
+ key(p.config, "config", false, false),
653
+ key(p.credConfig, "credConfig", false, true),
654
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
655
+ ], concat(SET_CREDENTIAL_CONFIG_DISCRIMINATOR, u64le(p.issuerFeeLamports, "issuerFee"), u64le(p.credentialFeeLamports, "credentialFee")));
656
+ }
657
+ /** `set_recommended_issuers(issuers)` — `Config.admin`; each listed Issuer
658
+ * account rides along read-only, in list order. */
659
+ export function buildSetRecommendedIssuersIx(p) {
660
+ if (p.issuers.length > MAX_RECOMMENDED_ISSUERS)
661
+ throw new Error("at most 32 recommended issuers");
662
+ return ix(p.programId, [
663
+ key(p.admin, "admin", true, true),
664
+ key(p.config, "config", false, false),
665
+ key(p.recommended, "recommended", false, true),
666
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
667
+ ...p.issuers.map((a, i) => key(a, `issuers[${i}]`, false, false)),
668
+ ], concat(SET_RECOMMENDED_ISSUERS_DISCRIMINATOR, u32le(p.issuers.length, "issuers length"), ...p.issuers.map((a, i) => toBytes32(a, `issuers[${i}]`))));
669
+ }