@x1id/resolve 0.23.0 → 0.44.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 +97 -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,452 @@
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 { encodeBase58, decodeBase58, decodeBase58_32 } from "./base58.js";
33
+ import { buildTokenizedTailKeys } from "./mintNft.js";
34
+ // Imported WITHOUT re-export (voucher.ts owns them) so `export *` can't collide.
35
+ import { MAX_VOUCHER_DURATION_SECS } from "./voucher.js";
36
+ // ---------------------------------------------------------------------------
37
+ // constants (mirror programs/x1-handles/src/{lib,state}.rs)
38
+ // ---------------------------------------------------------------------------
39
+ /** Seed prefix of the escrow PDA: `["gift", gift_key]`. */
40
+ export const GIFT_LINK_SEED = "gift";
41
+ /** Allocated size of a `GiftLink` (8-byte discriminator + `INIT_SPACE`). */
42
+ export const GIFT_LINK_ACCOUNT_LEN = 8 + 160;
43
+ /** Longest a gift link may stay open (same cap as vouchers: 366 days). */
44
+ export const MAX_GIFT_LINK_DURATION_SECS = MAX_VOUCHER_DURATION_SECS;
45
+ /** Where the app opens gift links. The secret goes after `#k=`. */
46
+ export const GIFT_LINK_BASE_URL = "https://app.x1id.io/gift/link";
47
+ /** A sensible client default for `expiresAt`: 30 days. */
48
+ export const DEFAULT_GIFT_LINK_TTL_SECS = 30 * 24 * 60 * 60;
49
+ /** Compute-unit limit to request for `claim_gift_link_name` (measured
50
+ * ~307k on litesvm with the real Metaplex program; same headroom as
51
+ * `REGISTER_TOKENIZED_CU_LIMIT`). */
52
+ export const CLAIM_GIFT_LINK_NAME_CU_LIMIT = 420_000;
53
+ /** `GiftLink.kind` (Borsh enum index). */
54
+ export const GIFT_KIND = { Xnt: 0, Name: 1 };
55
+ const d8 = (...b) => Uint8Array.from(b);
56
+ /** `sha256("global:create_gift_link")[..8]`. */
57
+ export const CREATE_GIFT_LINK_DISCRIMINATOR = d8(22, 78, 111, 117, 203, 197, 112, 35);
58
+ /** `sha256("global:claim_gift_link")[..8]`. */
59
+ export const CLAIM_GIFT_LINK_DISCRIMINATOR = d8(4, 52, 143, 19, 13, 156, 134, 62);
60
+ /** `sha256("global:claim_gift_link_name")[..8]`. */
61
+ export const CLAIM_GIFT_LINK_NAME_DISCRIMINATOR = d8(63, 204, 162, 126, 81, 187, 255, 7);
62
+ /** `sha256("global:refund_gift_link")[..8]`. */
63
+ export const REFUND_GIFT_LINK_DISCRIMINATOR = d8(193, 56, 128, 242, 206, 118, 202, 148);
64
+ /** `sha256("account:GiftLink")[..8]`. */
65
+ export const GIFT_LINK_ACCOUNT_DISCRIMINATOR = d8(87, 159, 144, 200, 207, 135, 54, 251);
66
+ /** Byte offsets inside a raw `GiftLink` (fixed up to `integrator`), for
67
+ * `getProgramAccounts` `memcmp` filters (e.g. a sender's open links). */
68
+ export const GIFT_LINK_OFFSETS = {
69
+ version: 8,
70
+ sender: 9,
71
+ giftKey: 41,
72
+ kind: 73,
73
+ amount: 74,
74
+ name: 82,
75
+ nameLen: 114,
76
+ handleType: 115,
77
+ integrator: 116,
78
+ };
79
+ /** Program error codes added for gift links (`RegistryError`). Gift links
80
+ * also reuse `VoucherNameRegistered` (6057), `BadVoucherExpiry` (6058),
81
+ * `PriceBelowRent` (6086), `ProtocolLockedDown` (6102) and
82
+ * `VoucherExpiryTooFar` (6112). */
83
+ export const GIFT_LINK_ERRORS = {
84
+ GiftAmountInvalid: 6151,
85
+ GiftLinkExpired: 6152,
86
+ GiftLinkWrongKind: 6153,
87
+ GiftLinkBadDestination: 6154,
88
+ GiftLinkNotRefundable: 6155,
89
+ GiftLinkNameReserved: 6156,
90
+ /** `gift_key == sender` (Sentinel run-011; 6158-6229 are upgrade 6's). */
91
+ GiftLinkKeyIsSender: 6231,
92
+ };
93
+ const SYSTEM_PROGRAM = "11111111111111111111111111111111";
94
+ const TE = new TextEncoder();
95
+ const U64_MAX = 0xffffffffffffffffn;
96
+ const I64_MAX = 0x7fffffffffffffffn;
97
+ // ---------------------------------------------------------------------------
98
+ // encoding helpers
99
+ // ---------------------------------------------------------------------------
100
+ function toBytes32(v, what) {
101
+ if (typeof v === "string") {
102
+ const b = decodeBase58_32(v);
103
+ if (!b)
104
+ throw new Error(`${what} is not a valid base58 address`);
105
+ return b;
106
+ }
107
+ if (v.length !== 32)
108
+ throw new Error(`${what} must be exactly 32 bytes`);
109
+ return v;
110
+ }
111
+ function toBase58(v, what) {
112
+ return encodeBase58(toBytes32(v, what));
113
+ }
114
+ function key(pubkey, what, isSigner, isWritable) {
115
+ return { pubkey: toBase58(pubkey, what), isSigner, isWritable };
116
+ }
117
+ function concat(...parts) {
118
+ const out = new Uint8Array(parts.reduce((a, p) => a + p.length, 0));
119
+ let o = 0;
120
+ for (const p of parts) {
121
+ out.set(p, o);
122
+ o += p.length;
123
+ }
124
+ return out;
125
+ }
126
+ function u64le(v) {
127
+ const out = new Uint8Array(8);
128
+ new DataView(out.buffer).setBigUint64(0, v, true);
129
+ return out;
130
+ }
131
+ function i64le(v) {
132
+ const out = new Uint8Array(8);
133
+ new DataView(out.buffer).setBigInt64(0, v, true);
134
+ return out;
135
+ }
136
+ function encString(s) {
137
+ const bytes = TE.encode(s);
138
+ const out = new Uint8Array(4 + bytes.length);
139
+ new DataView(out.buffer).setUint32(0, bytes.length, true);
140
+ out.set(bytes, 4);
141
+ return out;
142
+ }
143
+ function subtle() {
144
+ const c = globalThis.crypto;
145
+ if (!c?.subtle)
146
+ throw new Error("WebCrypto is not available; pass your own gift key");
147
+ return c.subtle;
148
+ }
149
+ /** PKCS#8 prefix of an Ed25519 private key (RFC 8410): the 32-byte seed follows. */
150
+ const PKCS8_ED25519_PREFIX = Uint8Array.from([
151
+ 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
152
+ ]);
153
+ function b64urlToBytes(s) {
154
+ const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4);
155
+ const bin = atob(b64);
156
+ const out = new Uint8Array(bin.length);
157
+ for (let i = 0; i < bin.length; i++)
158
+ out[i] = bin.charCodeAt(i);
159
+ return out;
160
+ }
161
+ /** Derive the ed25519 public key of a 32-byte seed (WebCrypto). */
162
+ export async function giftKeyFromSeed(seed) {
163
+ if (seed.length !== 32)
164
+ throw new Error("gift key seed must be exactly 32 bytes");
165
+ const s = subtle();
166
+ const priv = await s.importKey("pkcs8", concat(PKCS8_ED25519_PREFIX, seed), { name: "Ed25519" }, true, ["sign"]);
167
+ const jwk = (await s.exportKey("jwk", priv));
168
+ if (!jwk.x)
169
+ throw new Error("WebCrypto did not return the Ed25519 public key");
170
+ const pub = b64urlToBytes(jwk.x);
171
+ if (pub.length !== 32)
172
+ throw new Error("unexpected Ed25519 public key length");
173
+ return { giftKey: encodeBase58(pub), secretKey: concat(seed, pub) };
174
+ }
175
+ /**
176
+ * Generate a fresh ephemeral gift key (CSPRNG seed, WebCrypto Ed25519). One
177
+ * per link; never reuse it.
178
+ */
179
+ export async function newGiftLinkKey() {
180
+ const c = globalThis.crypto;
181
+ if (!c?.getRandomValues)
182
+ throw new Error("WebCrypto is not available; pass your own gift key");
183
+ const seed = c.getRandomValues(new Uint8Array(32));
184
+ try {
185
+ return await giftKeyFromSeed(seed);
186
+ }
187
+ finally {
188
+ seed.fill(0);
189
+ }
190
+ }
191
+ /** The shareable link: `<base>#k=<base58 64-byte secret key>`. The secret is
192
+ * in the FRAGMENT so it never reaches a server log. */
193
+ export function giftLinkUrl(key, base = GIFT_LINK_BASE_URL) {
194
+ if (key.secretKey.length !== 64)
195
+ throw new Error("secretKey must be the 64-byte form");
196
+ if (base.includes("#"))
197
+ throw new Error("base URL must not carry a fragment");
198
+ return `${base}#k=${encodeBase58(key.secretKey)}`;
199
+ }
200
+ /**
201
+ * Read the gift key back out of a link (or a bare `#k=...` fragment). Accepts
202
+ * the 64-byte secret key (what {@link giftLinkUrl} writes) or a 32-byte seed.
203
+ * Re-derives the public key (WebCrypto) and, for the 64-byte form, refuses a
204
+ * secret whose embedded public key does not match its seed. Null when the
205
+ * link has no well-formed key.
206
+ */
207
+ export async function parseGiftLinkUrl(link) {
208
+ const hash = link.includes("#") ? link.slice(link.indexOf("#") + 1) : link;
209
+ let k = null;
210
+ for (const part of hash.split("&")) {
211
+ if (part.startsWith("k="))
212
+ k = decodeURIComponent(part.slice(2));
213
+ }
214
+ if (!k)
215
+ return null;
216
+ const raw = decodeBase58(k);
217
+ if (!raw || (raw.length !== 32 && raw.length !== 64))
218
+ return null;
219
+ const derived = await giftKeyFromSeed(raw.slice(0, 32));
220
+ if (raw.length === 64 && encodeBase58(raw.slice(32)) !== derived.giftKey)
221
+ return null;
222
+ return derived;
223
+ }
224
+ // ---------------------------------------------------------------------------
225
+ // PDA + decoder
226
+ // ---------------------------------------------------------------------------
227
+ /** The escrow: `["gift", gift_key]`. */
228
+ export async function findGiftLinkPda(find, programId, giftKey) {
229
+ return find([TE.encode(GIFT_LINK_SEED), toBytes32(giftKey, "giftKey")], toBase58(programId, "programId"));
230
+ }
231
+ /**
232
+ * Decode a raw `GiftLink` (discriminator included) in the Borsh order of
233
+ * state.rs: version u8 | sender | gift_key | kind u8 | amount u64 |
234
+ * name [u8;32] | name_len u8 | handle_type u8 | integrator Option<Pubkey> |
235
+ * integrator_rate_bps u16 | expires_at i64 | created_at i64 | bump u8.
236
+ * Null when the bytes are not a `GiftLink`.
237
+ */
238
+ export function decodeGiftLink(data) {
239
+ if (data.length < 8 + 1 + 32 + 32 + 1 + 8 + 32 + 1 + 1 + 1 + 2 + 8 + 8 + 1)
240
+ return null;
241
+ for (let i = 0; i < 8; i++)
242
+ if (data[i] !== GIFT_LINK_ACCOUNT_DISCRIMINATOR[i])
243
+ return null;
244
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
245
+ let o = 8;
246
+ const version = data[o];
247
+ o += 1;
248
+ const sender = encodeBase58(data.slice(o, o + 32));
249
+ o += 32;
250
+ const giftKey = encodeBase58(data.slice(o, o + 32));
251
+ o += 32;
252
+ const kindByte = data[o];
253
+ o += 1;
254
+ if (kindByte > 1)
255
+ return null;
256
+ const amount = view.getBigUint64(o, true);
257
+ o += 8;
258
+ const nameBytes = data.slice(o, o + 32);
259
+ o += 32;
260
+ const nameLen = data[o];
261
+ o += 1;
262
+ if (nameLen > 32)
263
+ return null;
264
+ const handleType = data[o];
265
+ o += 1;
266
+ const tag = data[o];
267
+ o += 1;
268
+ let integrator = null;
269
+ if (tag === 1) {
270
+ if (data.length < o + 32 + 2 + 8 + 8 + 1)
271
+ return null;
272
+ integrator = encodeBase58(data.slice(o, o + 32));
273
+ o += 32;
274
+ }
275
+ else if (tag !== 0)
276
+ return null;
277
+ const integratorRateBps = view.getUint16(o, true);
278
+ o += 2;
279
+ const expiresAt = view.getBigInt64(o, true);
280
+ o += 8;
281
+ const createdAt = view.getBigInt64(o, true);
282
+ o += 8;
283
+ const bump = data[o];
284
+ return {
285
+ version,
286
+ sender,
287
+ giftKey,
288
+ kind: kindByte === 0 ? "xnt" : "name",
289
+ amount,
290
+ name: new TextDecoder().decode(nameBytes.slice(0, nameLen)),
291
+ handleType,
292
+ integrator,
293
+ integratorRateBps,
294
+ expiresAt,
295
+ createdAt,
296
+ bump,
297
+ };
298
+ }
299
+ /** Fetch + decode a gift link by its escrow address (null if absent: never
300
+ * created, or already claimed / refunded). */
301
+ export async function fetchGiftLink(rpc, address, opts = {}) {
302
+ const addr = toBase58(address, "address");
303
+ const res = (await rpc("getAccountInfo", [addr, { encoding: "base64", commitment: opts.commitment ?? "confirmed" }]));
304
+ const v = res?.value;
305
+ if (!v)
306
+ return null;
307
+ if (opts.programId !== undefined && v.owner !== toBase58(opts.programId, "programId")) {
308
+ throw new Error("account is not owned by the registry program");
309
+ }
310
+ const bin = atob(v.data[0]);
311
+ const data = new Uint8Array(bin.length);
312
+ for (let i = 0; i < bin.length; i++)
313
+ data[i] = bin.charCodeAt(i);
314
+ const gift = decodeGiftLink(data);
315
+ if (!gift)
316
+ throw new Error("account is not an X1ID gift link");
317
+ return { address: addr, gift, lamports: BigInt(v.lamports) };
318
+ }
319
+ /** `now >= expiresAt`: claims fail, anyone may refund to the sender. */
320
+ export function isGiftLinkExpired(g, nowSecs) {
321
+ return BigInt(nowSecs) >= g.expiresAt;
322
+ }
323
+ // ---------------------------------------------------------------------------
324
+ // instruction builders
325
+ // ---------------------------------------------------------------------------
326
+ /** The Borsh arguments of `create_gift_link` (after the discriminator):
327
+ * `kind u8, amount u64, name String, handle_type u8, expires_at i64`. */
328
+ export function encodeCreateGiftLinkArgs(a) {
329
+ if (typeof a.amount !== "bigint" || a.amount < 0n || a.amount > U64_MAX)
330
+ throw new Error("amount must be a u64 bigint");
331
+ if (typeof a.expiresAt !== "bigint" || a.expiresAt <= 0n || a.expiresAt > I64_MAX) {
332
+ throw new Error("expiresAt must be a positive i64 bigint (unix seconds)");
333
+ }
334
+ if (!Number.isInteger(a.handleType) || a.handleType < 0 || a.handleType > 255)
335
+ throw new Error("handleType must be a u8");
336
+ return concat(Uint8Array.of(GIFT_KIND[a.kind === "xnt" ? "Xnt" : "Name"]), u64le(a.amount), encString(a.name), Uint8Array.of(a.handleType), i64le(a.expiresAt));
337
+ }
338
+ /**
339
+ * `create_gift_link(kind, amount, name, handle_type, expires_at)`. Accounts
340
+ * (`CreateGiftLink`): sender(s,w), gift_key(s), config, gift(w),
341
+ * system_program, then (name gifts) handle, voucher and the optional
342
+ * integrator pair. A name gift's escrow is priced by the program (the live
343
+ * price snapshot); `amount` is sent as 0.
344
+ */
345
+ export function buildCreateGiftLinkIx(p) {
346
+ const sender = toBase58(p.sender, "sender");
347
+ const giftKey = toBase58(p.giftKey, "giftKey");
348
+ if (sender === giftKey)
349
+ throw new Error("the gift key must be a fresh key, not the sender");
350
+ const keys = [
351
+ key(sender, "sender", true, true),
352
+ key(giftKey, "giftKey", true, false),
353
+ key(p.config, "config", false, false),
354
+ key(p.gift, "gift", false, true),
355
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
356
+ ];
357
+ let args;
358
+ if (p.kind === "xnt") {
359
+ if (typeof p.amount !== "bigint" || p.amount <= 0n)
360
+ throw new Error("amount must be a positive bigint (lamports)");
361
+ args = encodeCreateGiftLinkArgs({ kind: "xnt", amount: p.amount, name: "", handleType: 0, expiresAt: p.expiresAt });
362
+ }
363
+ else {
364
+ if (!p.name || p.name.startsWith("@"))
365
+ throw new Error("name must be the canonical bare name (no @)");
366
+ keys.push(key(p.handle, "handle", false, false), key(p.voucher, "voucher", false, false));
367
+ if (p.integratorAccounts)
368
+ keys.push(...p.integratorAccounts);
369
+ args = encodeCreateGiftLinkArgs({
370
+ kind: "name",
371
+ amount: 0n,
372
+ name: p.name,
373
+ handleType: p.handleType,
374
+ expiresAt: p.expiresAt,
375
+ });
376
+ }
377
+ return { programId: toBase58(p.programId, "programId"), keys, data: concat(CREATE_GIFT_LINK_DISCRIMINATOR, args) };
378
+ }
379
+ /** `claim_gift_link()` (XNT gifts). Accounts (`ClaimGiftLink`): gift_key(s),
380
+ * config, gift(w), sender(w), destination(w). The fee payer is whoever
381
+ * submits (not an account of this instruction). */
382
+ export function buildClaimGiftLinkIx(p) {
383
+ const giftKey = toBase58(p.giftKey, "giftKey");
384
+ const destination = toBase58(p.destination, "destination");
385
+ if (destination === giftKey)
386
+ throw new Error("destination must not be the gift key (its secret is in the link)");
387
+ return {
388
+ programId: toBase58(p.programId, "programId"),
389
+ keys: [
390
+ key(giftKey, "giftKey", true, false),
391
+ key(p.config, "config", false, false),
392
+ key(p.gift, "gift", false, true),
393
+ key(p.sender, "sender", false, true),
394
+ key(destination, "destination", false, true),
395
+ ],
396
+ data: CLAIM_GIFT_LINK_DISCRIMINATOR.slice(),
397
+ };
398
+ }
399
+ /**
400
+ * `claim_gift_link_name(name)` (name gifts): registers the name to `owner` as
401
+ * a tokenized pNFT, exactly like `claim_voucher_tokenized`. Accounts
402
+ * (`ClaimGiftLinkName`): claimer(s,w), gift_key(s), config(w), treasury(w),
403
+ * gift(w), sender(w), owner, handle(w), then the 14-account pNFT tail, then
404
+ * the optional integrator(w). Prepend a ComputeBudget
405
+ * `SetComputeUnitLimit(CLAIM_GIFT_LINK_NAME_CU_LIMIT)`.
406
+ */
407
+ export function buildClaimGiftLinkNameIx(p) {
408
+ const giftKey = toBase58(p.giftKey, "giftKey");
409
+ const owner = toBase58(p.owner, "owner");
410
+ if (owner === giftKey)
411
+ throw new Error("owner must not be the gift key (its secret is in the link)");
412
+ const keys = [
413
+ key(p.claimer, "claimer", true, true),
414
+ key(giftKey, "giftKey", true, false),
415
+ key(p.config, "config", false, true),
416
+ key(p.treasury, "treasury", false, true),
417
+ key(p.gift, "gift", false, true),
418
+ key(p.sender, "sender", false, true),
419
+ key(owner, "owner", false, false),
420
+ key(p.handle, "handle", false, true),
421
+ ...buildTokenizedTailKeys(p),
422
+ ];
423
+ if (p.integrator !== undefined)
424
+ keys.push(key(p.integrator, "integrator", false, true));
425
+ return {
426
+ programId: toBase58(p.programId, "programId"),
427
+ keys,
428
+ data: concat(CLAIM_GIFT_LINK_NAME_DISCRIMINATOR, encString(p.name)),
429
+ };
430
+ }
431
+ /** `refund_gift_link()`. Accounts (`RefundGiftLink`): caller(s), gift(w),
432
+ * sender(w). */
433
+ export function buildRefundGiftLinkIx(p) {
434
+ return {
435
+ programId: toBase58(p.programId, "programId"),
436
+ keys: [key(p.caller, "caller", true, false), key(p.gift, "gift", false, true), key(p.sender, "sender", false, true)],
437
+ data: REFUND_GIFT_LINK_DISCRIMINATOR.slice(),
438
+ };
439
+ }
440
+ /** Plain-language sentence for a gift-link error code (6151-6156, 6231), or null. */
441
+ export function giftLinkErrorMessage(code) {
442
+ switch (code) {
443
+ case 6151: return "The gift amount is too small (an XNT gift must be at least about 0.00089 XNT).";
444
+ case 6152: return "This gift link has expired.";
445
+ case 6153: return "This gift link holds something else (XNT vs a name).";
446
+ case 6154: return "That wallet can't receive this gift.";
447
+ case 6155: return "Only the sender can cancel this gift before it expires.";
448
+ case 6156: return "This name already has a pending gift voucher.";
449
+ case 6231: return "The gift link needs its own fresh key, not your wallet.";
450
+ default: return null;
451
+ }
452
+ }
package/dist/index.d.ts CHANGED
@@ -38,6 +38,7 @@ export * from "./gate.js";
38
38
  export * from "./gateClient.js";
39
39
  export * from "./textRecords.js";
40
40
  export * from "./lock.js";
41
+ export * from "./recovery.js";
41
42
  export * from "./integrator.js";
42
43
  export * from "./voucher.js";
43
44
  export * from "./agent.js";
@@ -48,6 +49,7 @@ export * from "./lease.js";
48
49
  export * from "./accounts.js";
49
50
  export * from "./pause.js";
50
51
  export * from "./x402.js";
52
+ export * from "./x402Receipt.js";
51
53
  export * from "./recordWrite.js";
52
54
  export * from "./clearRecords.js";
53
55
  export * from "./commitReveal.js";
@@ -58,6 +60,7 @@ export * from "./signin.js";
58
60
  export * from "./connect.js";
59
61
  export * from "./siws.js";
60
62
  export * from "./domainProof.js";
63
+ export * from "./shield.js";
61
64
  export { scopedTldCandidate, namespaceIsActive, makeScopedHandleDeriver, type ScopedCandidate, type ScopedHandleDeriver, } from "./scoped.js";
62
65
  import { type Chain, type Resolved } from "./types.js";
63
66
  import { type ScopedHandleDeriver } from "./scoped.js";
@@ -128,4 +131,14 @@ export declare function createResolver(config: ResolverConfig): Resolver;
128
131
  export * from "./adminMarket.js";
129
132
  export * from "./namespaceOverride.js";
130
133
  export * from "./namespacePricing.js";
134
+ export * from "./pay.js";
135
+ export * from "./subscription.js";
136
+ export * from "./giftLink.js";
137
+ export * from "./credentials.js";
138
+ export * from "./agentPolicy.js";
139
+ export * from "./recoveryNft.js";
140
+ export * from "./nsGate.js";
141
+ export * from "./checkout.js";
131
142
  export * from "./universal.js";
143
+ export * from "./oidc.js";
144
+ export * from "./loginButton.js";
package/dist/index.js CHANGED
@@ -38,6 +38,9 @@ export * from "./gate.js";
38
38
  export * from "./gateClient.js";
39
39
  export * from "./textRecords.js";
40
40
  export * from "./lock.js";
41
+ // Recovery 2 (WP 8868): set/initiate/cancel/complete_recovery builders +
42
+ // fetchRecoveryState (recovery + lock state with time remaining).
43
+ export * from "./recovery.js";
41
44
  export * from "./integrator.js";
42
45
  export * from "./voucher.js";
43
46
  export * from "./agent.js";
@@ -55,6 +58,9 @@ export * from "./lease.js";
55
58
  export * from "./accounts.js";
56
59
  export * from "./pause.js";
57
60
  export * from "./x402.js";
61
+ // x402 payment receipts on chain (#8324 / [Pay 10]): record_x402_receipt,
62
+ // its X402ReceiptRecorded event and the AgentPaymentStats tally.
63
+ export * from "./x402Receipt.js";
58
64
  export * from "./recordWrite.js";
59
65
  export * from "./clearRecords.js";
60
66
  export * from "./commitReveal.js";
@@ -65,6 +71,7 @@ export * from "./signin.js";
65
71
  export * from "./connect.js";
66
72
  export * from "./siws.js";
67
73
  export * from "./domainProof.js";
74
+ export * from "./shield.js";
68
75
  // Named (not `export *`): `NAMESPACE_MAX_LEN` is already exported by
69
76
  // adminMarket.js, and two `export *` sharing a name would make it ambiguous
70
77
  // (dropped from the package's public surface). Re-export the scoped API, minus
@@ -818,7 +825,32 @@ export function createResolver(config) {
818
825
  export * from "./adminMarket.js";
819
826
  export * from "./namespaceOverride.js";
820
827
  export * from "./namespacePricing.js";
828
+ // X1ID Pay phase 1: on-chain invoices (docs/specs/x1id-pay-onchain.md).
829
+ export * from "./pay.js";
830
+ // X1ID Pay phase 2: SPL pull subscriptions (docs/specs/x1id-pay-onchain.md).
831
+ export * from "./subscription.js";
832
+ // Open gift links: XNT or a prepaid name, claimed by the link's ephemeral key
833
+ // (docs/specs/gift-links.md).
834
+ export * from "./giftLink.js";
835
+ // X1ID Verify: third-party credentials (docs/specs/x1id-credentials.md,
836
+ // program upgrade 6). Never a blue tick; gate with `{ credential }` (gate.ts).
837
+ export * from "./credentials.js";
838
+ // Program upgrade 6: agent spend policies (docs/specs/agent-spend-policy.md),
839
+ // recovery for tokenized names (docs/specs/recovery-tokenized.md) and the TLD
840
+ // token gate (docs/specs/tld-communities.md §2).
841
+ export * from "./agentPolicy.js";
842
+ export * from "./recoveryNft.js";
843
+ export * from "./nsGate.js";
844
+ // [Pay 7] checkout widget: the merchant ↔ X1ID popup postMessage protocol
845
+ // (docs/x1id-pay-checkout.md). Server-side verification is `verifyInvoicePaid`.
846
+ export * from "./checkout.js";
821
847
  // The X1ID Universal Resolver (#8475): cross-namespace resolution (.sol via SNS,
822
848
  // .eth via ENS) alongside the native path. Additive — the native resolver above
823
849
  // is untouched; `createUniversalResolver` composes it with external adapters.
824
850
  export * from "./universal.js";
851
+ // [Auth 9] "Sign in with X1ID": OpenID Connect relying-party client for
852
+ // auth.x1id.io (code + PKCE S256, state, nonce, ID-token verification against
853
+ // the JWKS, /me) and the framework-free button (no DOM access at import).
854
+ // Also importable on their own: `@x1id/resolve/oidc`, `@x1id/resolve/login-button`.
855
+ export * from "./oidc.js";
856
+ export * from "./loginButton.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The "Sign in with X1ID" button — framework-free (no React), for the OIDC
3
+ * flow in ./oidc.ts. Two forms:
4
+ *
5
+ * ```ts
6
+ * // 1. A function that returns a <button> (append it anywhere):
7
+ * const client = createX1idOidcClient({ clientId: "x1c_…", redirectUri: "https://app.example/callback" });
8
+ * document.querySelector("#login")!.append(createX1idLoginButton({ client }));
9
+ *
10
+ * // 2. A custom element (register once, then use markup):
11
+ * defineX1idLoginButton();
12
+ * // <x1id-login-button client-id="x1c_…" redirect-uri="https://app.example/callback"></x1id-login-button>
13
+ * ```
14
+ *
15
+ * Nothing touches the DOM at import time (safe to import on a server, and
16
+ * tree-shaken away if unused). Accessible by construction: a native
17
+ * `<button type="button">` (keyboard + focus for free), a visible
18
+ * `:focus-visible` ring, `aria-busy` while the redirect starts, the mark is
19
+ * decorative (`alt=""`), light/dark follow `prefers-color-scheme` unless forced,
20
+ * Windows high-contrast (`forced-colors`) and `prefers-reduced-motion` are
21
+ * honored. Styles go in a constructable stylesheet where supported (not
22
+ * blocked by a strict `style-src` CSP), else one `<style>` element.
23
+ *
24
+ * Brand: the label is "Sign in with X1ID" — X1ID is one word, uppercase. Don't
25
+ * recolor or redraw the mark.
26
+ */
27
+ import { type X1idAuthorizationOptions, type X1idOidcClient, type X1idOidcConfig } from "./oidc.js";
28
+ export declare const X1ID_LOGIN_LABEL = "Sign in with X1ID";
29
+ export declare const X1ID_LOGIN_BUTTON_TAG = "x1id-login-button";
30
+ export type X1idButtonTheme = "auto" | "light" | "dark";
31
+ export interface X1idLoginButtonOptions {
32
+ /** Click starts the redirect flow with this client ({@link beginX1idLogin}). */
33
+ readonly client?: X1idOidcClient;
34
+ /** Per-request authorization options (prompt, scope, …). */
35
+ readonly authorization?: X1idAuthorizationOptions;
36
+ /** Your own click handler instead of `client` (e.g. a server-side login
37
+ * route: `() => location.assign("/auth/x1id/start")`). */
38
+ readonly onClick?: (ev: MouseEvent) => void | Promise<void>;
39
+ /** Called if starting the login fails (default: console.error). The button
40
+ * is re-enabled. */
41
+ readonly onError?: (err: unknown) => void;
42
+ /** "auto" (default) follows prefers-color-scheme; "light"/"dark" force it. */
43
+ readonly theme?: X1idButtonTheme;
44
+ /** Translated label. Keep the brand as "X1ID". Default "Sign in with X1ID". */
45
+ readonly label?: string;
46
+ readonly disabled?: boolean;
47
+ /** Injected document (tests). Default `globalThis.document`. */
48
+ readonly document?: Document;
49
+ }
50
+ export declare const X1ID_LOGIN_BUTTON_CSS = "\n:host{display:inline-block}\n:host([hidden]){display:none}\n.x1id-login{display:inline-flex;align-items:center;justify-content:center;gap:.6em;\n box-sizing:border-box;min-height:44px;margin:0;padding:.6em 1.1em;\n font:inherit;font-size:1rem;font-weight:600;line-height:1.2;letter-spacing:0;\n cursor:pointer;border-radius:8px;border:1px solid rgba(15,20,32,.16);\n background:#fff;color:#0f1420;-webkit-font-smoothing:antialiased;\n transition:background-color .12s ease,border-color .12s ease}\n.x1id-login:hover{background:#f6f8fb;border-color:rgba(15,20,32,.28)}\n.x1id-login:active{background:#eef1f6}\n.x1id-login:focus-visible{outline:2px solid #0a84ff;outline-offset:2px}\n.x1id-login:disabled{opacity:.55;cursor:default}\n.x1id-login[aria-busy=\"true\"]{cursor:progress}\n.x1id-login-logo{width:20px;height:20px;border-radius:5px;flex:0 0 auto;display:block}\n.x1id-login-label{white-space:nowrap}\n.x1id-login--dark{background:#161b26;color:#f3f6fb;border-color:rgba(255,255,255,.16)}\n.x1id-login--dark:hover{background:#1d2431;border-color:rgba(255,255,255,.28)}\n.x1id-login--dark:active{background:#131822}\n@media (prefers-color-scheme:dark){\n .x1id-login:not(.x1id-login--light){background:#161b26;color:#f3f6fb;border-color:rgba(255,255,255,.16)}\n .x1id-login:not(.x1id-login--light):hover{background:#1d2431;border-color:rgba(255,255,255,.28)}\n .x1id-login:not(.x1id-login--light):active{background:#131822}\n}\n@media (prefers-reduced-motion:reduce){.x1id-login{transition:none}}\n@media (forced-colors:active){.x1id-login{border-color:ButtonText}.x1id-login:focus-visible{outline-color:Highlight}}\n";
51
+ /**
52
+ * Build a "Sign in with X1ID" `<button>`. Pass `client` (click → redirect to
53
+ * auth.x1id.io) or your own `onClick`. Injects its stylesheet once.
54
+ */
55
+ export declare function createX1idLoginButton(opts?: X1idLoginButtonOptions): HTMLButtonElement;
56
+ /** The custom element's attributes → client config (pure; exported for tests). */
57
+ export declare function loginButtonAttributes(get: (name: string) => string | null): {
58
+ config: X1idOidcConfig | null;
59
+ authorization: X1idAuthorizationOptions;
60
+ theme: X1idButtonTheme;
61
+ label: string | undefined;
62
+ };
63
+ /**
64
+ * Register `<x1id-login-button>` (idempotent). Attributes: `client-id`,
65
+ * `redirect-uri` (required unless you set `el.client`), `issuer`, `scope`,
66
+ * `prompt`, `theme` (auto|light|dark), `label`, `disabled`. A failure to start
67
+ * dispatches a bubbling `x1id-error` CustomEvent (`detail` = the error).
68
+ */
69
+ export declare function defineX1idLoginButton(tagName?: string): void;