@x1id/resolve 0.3.0 → 0.7.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.
package/dist/subname.d.ts CHANGED
@@ -259,6 +259,11 @@ export declare function fetchSubnames(rpc: RpcFn, programId: string, parentHandl
259
259
  * This does NOT apply layer 1 (subname-vs-parent). Use
260
260
  * {@link resolveSubnameRecords} for the complete, safe resolution — or gate
261
261
  * this call on {@link subnameIsLive} yourself.
262
+ *
263
+ * `recordsClearedAt` is fixed at `0n`, not the parent handle's: `clear_records`
264
+ * (#8139) only ever writes the PARENT `Handle` account's own extension — a
265
+ * subname is a separate account with no `records_cleared_at` of its own, so
266
+ * the parent's clear has no bearing on records keyed by the subname's pubkey.
262
267
  */
263
268
  export declare function fetchSubnameRecords(rpc: RpcFn, programId: string, subnameAccount: string, subnameCreatedAt: bigint): Promise<HandleRecord[]>;
264
269
  /**
package/dist/subname.js CHANGED
@@ -339,12 +339,17 @@ export async function fetchSubnames(rpc, programId, parentHandleAccount, parentR
339
339
  * This does NOT apply layer 1 (subname-vs-parent). Use
340
340
  * {@link resolveSubnameRecords} for the complete, safe resolution — or gate
341
341
  * this call on {@link subnameIsLive} yourself.
342
+ *
343
+ * `recordsClearedAt` is fixed at `0n`, not the parent handle's: `clear_records`
344
+ * (#8139) only ever writes the PARENT `Handle` account's own extension — a
345
+ * subname is a separate account with no `records_cleared_at` of its own, so
346
+ * the parent's clear has no bearing on records keyed by the subname's pubkey.
342
347
  */
343
348
  export async function fetchSubnameRecords(rpc, programId, subnameAccount, subnameCreatedAt) {
344
349
  // The record filter already includes size + Record discriminator; the
345
350
  // handle-field memcmp is the subname pubkey here. Reuse the exact scanner so
346
351
  // subname records and handle records can never decode differently.
347
- return fetchRecords(rpc, programId, subnameAccount, subnameCreatedAt);
352
+ return fetchRecords(rpc, programId, subnameAccount, subnameCreatedAt, 0n);
348
353
  }
349
354
  /**
350
355
  * Resolve a subname's LIVE address records — the ONLY safe entry point, both
@@ -45,6 +45,14 @@
45
45
  * There is deliberately no way to decode or fetch a text record through
46
46
  * this module without the handle's `registered_at` in hand.
47
47
  *
48
+ * # `records_cleared_at` (#8139) — the SECOND, independent staleness rule
49
+ *
50
+ * `clear_records` lets an owner bulk-invalidate every record RIGHT NOW,
51
+ * without a transfer — text records too. The same functions therefore also
52
+ * demand the handle's `recordsClearedAt` (0 if never cleared, from
53
+ * `ParsedHandle`), and a text record is `stale` when EITHER
54
+ * `updated_at < registeredAt` OR `updated_at < recordsClearedAt`.
55
+ *
48
56
  * # These records count (#7384)
49
57
  *
50
58
  * `create_text_record` increments — and `close_text_record` decrements —
@@ -140,15 +148,17 @@ export declare function textValueToString(value: Uint8Array): string | null;
140
148
  * value: Vec<u8> (u32 len + bytes, max 256) updated_at: i64(8) bump: u8(1)
141
149
  *
142
150
  * `registeredAt` is the owning Handle's `registered_at`, decoded by the
143
- * caller from the Handle account — it decides `stale`. There is
144
- * intentionally no overload without it (see the module docs).
151
+ * caller from the Handle account — it decides `stale`. `recordsClearedAt` is
152
+ * that same Handle's `recordsClearedAt` (0 if never cleared, #8139), a
153
+ * SECOND independent staleness anchor. There is intentionally no overload
154
+ * without either (see the module docs).
145
155
  *
146
156
  * Returns null for anything that is not a TextRecord: wrong length, wrong
147
157
  * discriminator, lengths that do not fit, or a key that is not valid UTF-8
148
158
  * (the program's Borsh `String` guarantees it is, so that is not a
149
159
  * TextRecord).
150
160
  */
151
- export declare function decodeTextRecord(raw: Uint8Array, account: string, registeredAt: bigint): HandleTextRecord | null;
161
+ export declare function decodeTextRecord(raw: Uint8Array, account: string, registeredAt: bigint, recordsClearedAt: bigint): HandleTextRecord | null;
152
162
  /** The text records the CURRENT owner actually has — `stale` ones excluded.
153
163
  * This is the list to render on a profile and count. */
154
164
  export declare function liveTextRecords(records: readonly HandleTextRecord[]): HandleTextRecord[];
@@ -162,12 +172,13 @@ export declare function liveTextRecords(records: readonly HandleTextRecord[]): H
162
172
  *
163
173
  * `registeredAt` is `Handle.registered_at` as decoded from the Handle
164
174
  * account the caller already has — the staleness rule needs it, and there
165
- * is no variant of this function without it. Every record is returned,
166
- * stale ones flagged, so an owner surface can show what a previous owner
167
- * left behind; anything that renders a profile takes
175
+ * is no variant of this function without it. `recordsClearedAt` is that same
176
+ * Handle's `recordsClearedAt` (0 if never cleared, #8139) — pass it through.
177
+ * Every record is returned, stale ones flagged, so an owner surface can show
178
+ * what a previous owner left behind; anything that renders a profile takes
168
179
  * {@link liveTextRecords}.
169
180
  */
170
- export declare function fetchTextRecords(rpc: RpcFn, programId: string, handleAccount: string, registeredAt: bigint): Promise<HandleTextRecord[]>;
181
+ export declare function fetchTextRecords(rpc: RpcFn, programId: string, handleAccount: string, registeredAt: bigint, recordsClearedAt: bigint): Promise<HandleTextRecord[]>;
171
182
  /** The delegate-aware trailing accounts every text-record builder shares —
172
183
  * the exact rules of `delegate.ts`'s module docs. */
173
184
  interface RecordEditAuthorityParams {
@@ -45,6 +45,14 @@
45
45
  * There is deliberately no way to decode or fetch a text record through
46
46
  * this module without the handle's `registered_at` in hand.
47
47
  *
48
+ * # `records_cleared_at` (#8139) — the SECOND, independent staleness rule
49
+ *
50
+ * `clear_records` lets an owner bulk-invalidate every record RIGHT NOW,
51
+ * without a transfer — text records too. The same functions therefore also
52
+ * demand the handle's `recordsClearedAt` (0 if never cleared, from
53
+ * `ParsedHandle`), and a text record is `stale` when EITHER
54
+ * `updated_at < registeredAt` OR `updated_at < recordsClearedAt`.
55
+ *
48
56
  * # These records count (#7384)
49
57
  *
50
58
  * `create_text_record` increments — and `close_text_record` decrements —
@@ -164,15 +172,17 @@ export function textValueToString(value) {
164
172
  * value: Vec<u8> (u32 len + bytes, max 256) updated_at: i64(8) bump: u8(1)
165
173
  *
166
174
  * `registeredAt` is the owning Handle's `registered_at`, decoded by the
167
- * caller from the Handle account — it decides `stale`. There is
168
- * intentionally no overload without it (see the module docs).
175
+ * caller from the Handle account — it decides `stale`. `recordsClearedAt` is
176
+ * that same Handle's `recordsClearedAt` (0 if never cleared, #8139), a
177
+ * SECOND independent staleness anchor. There is intentionally no overload
178
+ * without either (see the module docs).
169
179
  *
170
180
  * Returns null for anything that is not a TextRecord: wrong length, wrong
171
181
  * discriminator, lengths that do not fit, or a key that is not valid UTF-8
172
182
  * (the program's Borsh `String` guarantees it is, so that is not a
173
183
  * TextRecord).
174
184
  */
175
- export function decodeTextRecord(raw, account, registeredAt) {
185
+ export function decodeTextRecord(raw, account, registeredAt, recordsClearedAt) {
176
186
  if (raw.length !== TEXT_RECORD_LEN)
177
187
  return null;
178
188
  for (let i = 0; i < 8; i++) {
@@ -207,7 +217,7 @@ export function decodeTextRecord(raw, account, registeredAt) {
207
217
  value,
208
218
  text: textValueToString(value),
209
219
  updatedAt,
210
- stale: updatedAt < registeredAt,
220
+ stale: updatedAt < registeredAt || updatedAt < recordsClearedAt,
211
221
  };
212
222
  }
213
223
  /** The text records the CURRENT owner actually has — `stale` ones excluded.
@@ -225,12 +235,13 @@ export function liveTextRecords(records) {
225
235
  *
226
236
  * `registeredAt` is `Handle.registered_at` as decoded from the Handle
227
237
  * account the caller already has — the staleness rule needs it, and there
228
- * is no variant of this function without it. Every record is returned,
229
- * stale ones flagged, so an owner surface can show what a previous owner
230
- * left behind; anything that renders a profile takes
238
+ * is no variant of this function without it. `recordsClearedAt` is that same
239
+ * Handle's `recordsClearedAt` (0 if never cleared, #8139) — pass it through.
240
+ * Every record is returned, stale ones flagged, so an owner surface can show
241
+ * what a previous owner left behind; anything that renders a profile takes
231
242
  * {@link liveTextRecords}.
232
243
  */
233
- export async function fetchTextRecords(rpc, programId, handleAccount, registeredAt) {
244
+ export async function fetchTextRecords(rpc, programId, handleAccount, registeredAt, recordsClearedAt) {
234
245
  const res = (await rpc("getProgramAccounts", [
235
246
  programId,
236
247
  {
@@ -249,7 +260,7 @@ export async function fetchTextRecords(rpc, programId, handleAccount, registered
249
260
  const raw = new Uint8Array(bin.length);
250
261
  for (let i = 0; i < bin.length; i++)
251
262
  raw[i] = bin.charCodeAt(i);
252
- const decoded = decodeTextRecord(raw, a.pubkey, registeredAt);
263
+ const decoded = decodeTextRecord(raw, a.pubkey, registeredAt, recordsClearedAt);
253
264
  // Defence in depth: the memcmp filter should guarantee the handle
254
265
  // match, but a wrong offset would silently attribute someone else's
255
266
  // record to this handle. A malformed account is skipped, not fatal.
package/dist/wasm.d.ts CHANGED
@@ -54,5 +54,29 @@ export declare class WasmResolver {
54
54
  * holds a tokenized handle's NFT.
55
55
  */
56
56
  deriveAssociatedTokenAccount(owner: Uint8Array, mint: Uint8Array): Uint8Array | null;
57
+ /**
58
+ * 32-byte Metaplex Token Metadata PDA for a 32-byte mint under a given
59
+ * Token Metadata program id — seeds `["metadata", program, mint]`.
60
+ *
61
+ * The program id is a parameter (staged, like `deriveHandleAccount`'s)
62
+ * because X1 runs its own Token Metadata deployment
63
+ * (`MPL_TOKEN_METADATA_PROGRAM` in pnftTransfer.ts) — the canonical
64
+ * Solana-mainnet id would derive addresses no X1 account lives at.
65
+ */
66
+ deriveMetadataAccount(mint: Uint8Array, programId: Uint8Array): Uint8Array | null;
67
+ /**
68
+ * 32-byte Metaplex Master Edition PDA for a 32-byte mint under a given
69
+ * Token Metadata program id — seeds `["metadata", program, mint,
70
+ * "edition"]`. Same program-id rationale as `deriveMetadataAccount`.
71
+ */
72
+ deriveMasterEditionAccount(mint: Uint8Array, programId: Uint8Array): Uint8Array | null;
73
+ /**
74
+ * 32-byte Metaplex `TokenRecord` PDA for a 32-byte mint and a 32-byte token
75
+ * ACCOUNT (the ATA — not its owner) under a given Token Metadata program id
76
+ * — seeds `["metadata", program, mint, "token_record", token]`,
77
+ * byte-for-byte `mpl_token_metadata::accounts::TokenRecord::find_pda`.
78
+ * Needed on BOTH sides of a `ProgrammableNonFungible` transfer.
79
+ */
80
+ deriveTokenRecordAccount(mint: Uint8Array, token: Uint8Array, programId: Uint8Array): Uint8Array | null;
57
81
  }
58
82
  export {};
package/dist/wasm.js CHANGED
@@ -101,4 +101,49 @@ export class WasmResolver {
101
101
  const n = this.#write(joined);
102
102
  return this.#x.derive_associated_token_account(n) === 1 ? this.#read() : null;
103
103
  }
104
+ /**
105
+ * 32-byte Metaplex Token Metadata PDA for a 32-byte mint under a given
106
+ * Token Metadata program id — seeds `["metadata", program, mint]`.
107
+ *
108
+ * The program id is a parameter (staged, like `deriveHandleAccount`'s)
109
+ * because X1 runs its own Token Metadata deployment
110
+ * (`MPL_TOKEN_METADATA_PROGRAM` in pnftTransfer.ts) — the canonical
111
+ * Solana-mainnet id would derive addresses no X1 account lives at.
112
+ */
113
+ deriveMetadataAccount(mint, programId) {
114
+ if (mint.length !== 32 || programId.length !== 32)
115
+ return null;
116
+ new Uint8Array(this.#x.memory.buffer, this.#x.program_ptr(), 32).set(programId);
117
+ const n = this.#write(mint);
118
+ return this.#x.derive_metadata_account(n) === 1 ? this.#read() : null;
119
+ }
120
+ /**
121
+ * 32-byte Metaplex Master Edition PDA for a 32-byte mint under a given
122
+ * Token Metadata program id — seeds `["metadata", program, mint,
123
+ * "edition"]`. Same program-id rationale as `deriveMetadataAccount`.
124
+ */
125
+ deriveMasterEditionAccount(mint, programId) {
126
+ if (mint.length !== 32 || programId.length !== 32)
127
+ return null;
128
+ new Uint8Array(this.#x.memory.buffer, this.#x.program_ptr(), 32).set(programId);
129
+ const n = this.#write(mint);
130
+ return this.#x.derive_master_edition_account(n) === 1 ? this.#read() : null;
131
+ }
132
+ /**
133
+ * 32-byte Metaplex `TokenRecord` PDA for a 32-byte mint and a 32-byte token
134
+ * ACCOUNT (the ATA — not its owner) under a given Token Metadata program id
135
+ * — seeds `["metadata", program, mint, "token_record", token]`,
136
+ * byte-for-byte `mpl_token_metadata::accounts::TokenRecord::find_pda`.
137
+ * Needed on BOTH sides of a `ProgrammableNonFungible` transfer.
138
+ */
139
+ deriveTokenRecordAccount(mint, token, programId) {
140
+ if (mint.length !== 32 || token.length !== 32 || programId.length !== 32)
141
+ return null;
142
+ new Uint8Array(this.#x.memory.buffer, this.#x.program_ptr(), 32).set(programId);
143
+ const joined = new Uint8Array(64);
144
+ joined.set(mint, 0);
145
+ joined.set(token, 32);
146
+ const n = this.#write(joined);
147
+ return this.#x.derive_token_record_account(n) === 1 ? this.#read() : null;
148
+ }
104
149
  }
package/dist/x402.d.ts ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * x402 ("HTTP 402 Payment Required") payment requirements — the pure,
3
+ * dependency-free half of x1id's x402 support. Types and the
4
+ * `PaymentRequirements` builder live here; actual transaction construction
5
+ * and verification need real Solana transaction parsing (`@solana/web3.js`)
6
+ * and live in `mcp/src/x402/` instead, matching this SDK's established
7
+ * boundary (zero dependencies, no crypto/serialization — see delegate.ts's
8
+ * module docs for the same reasoning applied elsewhere).
9
+ *
10
+ * Field names and the `"exact"` scheme's wire shape are taken verbatim from
11
+ * the x402 Foundation's own SVM scheme spec
12
+ * (x402-foundation/x402, specs/schemes/exact/scheme_exact_svm.md, and the
13
+ * Go reference implementation under go/mechanisms/svm/exact/), verified
14
+ * 2026-09-22 — NOT invented. See `docs/x402.md` for the full design,
15
+ * including what's explicitly OUT of scope here (a facilitator).
16
+ *
17
+ * # The one x1id-specific choice: the network identifier
18
+ *
19
+ * x402's Solana networks use `"solana:<genesis-hash>"` (CAIP-2-shaped). X1
20
+ * is SVM-compatible but its OWN chain — not Solana — so it gets its own
21
+ * namespace under the identical convention: `"x1:<genesis-hash>"`. This is
22
+ * NOT an x402-foundation-registered network; it's a principled extension of
23
+ * their own pattern to a new SVM-compatible chain, not a claim of official
24
+ * support. {@link X1_TESTNET_NETWORK} is the live testnet genesis hash,
25
+ * verified against `getGenesisHash` 2026-09-22.
26
+ */
27
+ /** X1 testnet's x402 network identifier (`"x1:<genesis-hash>"`, verified
28
+ * live 2026-09-22 via `getGenesisHash`). */
29
+ export declare const X1_TESTNET_NETWORK = "x1:C7ucgdDEhxLTpXHhWSZxavSVmaNTUJWwT5iTdeaviDho";
30
+ /** The x402 scheme x1id implements — "exact": pay a fixed amount of a named
31
+ * asset to a named recipient. (x402 also defines "upto" for usage-based
32
+ * pricing; not implemented here.) */
33
+ export declare const X402_SCHEME_EXACT = "exact";
34
+ export declare const X402_VERSION = 2;
35
+ /** One entry of a 402 response's `accepts[]` — the SVM `"exact"` scheme's
36
+ * `PaymentRequirements`, field names verbatim from the spec. */
37
+ export interface X402PaymentRequirements {
38
+ readonly scheme: "exact";
39
+ /** `"x1:<genesis-hash>"` — see the module docs. */
40
+ readonly network: string;
41
+ /** Smallest-unit amount, as a DECIMAL STRING (e.g. lamports, or an SPL
42
+ * token's atomic units) — never a number (precision). */
43
+ readonly amount: string;
44
+ /** The SPL/Token-2022 mint's base58 address. */
45
+ readonly asset: string;
46
+ /** The recipient's base58 address (a wallet, not the token account —
47
+ * the payer derives the destination ATA from this + `asset`). */
48
+ readonly payTo: string;
49
+ readonly maxTimeoutSeconds: number;
50
+ readonly extra: {
51
+ /** The sponsor that will co-sign as fee payer, base58. REQUIRED by the
52
+ * spec for the client to build a transaction at all — x1id's v1 has
53
+ * no sponsor (see docs/x402.md): this SDK always sets it to the
54
+ * BUYER's own address (self-sponsored, no facilitator, no custody). */
55
+ readonly feePayer: string;
56
+ /** UTF-8, <=256 bytes. Lets a seller correlate the on-chain payment to
57
+ * an invoice without a unique deposit address per sale. */
58
+ readonly memo?: string;
59
+ readonly recentBlockhash?: string;
60
+ readonly lastValidBlockHeight?: string;
61
+ };
62
+ }
63
+ /** The 402 response body's `accepts[]` wrapper — what a resource server
64
+ * actually returns. */
65
+ export interface X402PaymentRequiredResponse {
66
+ readonly x402Version: typeof X402_VERSION;
67
+ readonly accepts: readonly X402PaymentRequirements[];
68
+ }
69
+ /** What the client sends back (as the `payload.transaction` field, once
70
+ * base64-encoded — see `mcp/src/x402/transaction.ts` for actually
71
+ * building and encoding one; this type documents the wire shape only). */
72
+ export interface X402PaymentPayload {
73
+ readonly x402Version: typeof X402_VERSION;
74
+ readonly accepted: X402PaymentRequirements;
75
+ readonly payload: {
76
+ /** Base64-encoded, serialized, partially-signed versioned transaction. */
77
+ readonly transaction: string;
78
+ };
79
+ }
80
+ export interface BuildX402RequirementsParams {
81
+ /** The recipient's base58 address. */
82
+ readonly payTo: string;
83
+ /** The SPL/Token-2022 mint's base58 address. */
84
+ readonly asset: string;
85
+ /** Smallest-unit amount as a decimal string. */
86
+ readonly amount: string;
87
+ /** X1id v1 has no sponsoring facilitator (see the module docs):
88
+ * the buyer pays their own transaction fee, so `extra.feePayer` is
89
+ * always set to this same buyer address. Pass the address the caller
90
+ * expects to pay from. */
91
+ readonly buyer: string;
92
+ readonly network?: string;
93
+ readonly maxTimeoutSeconds?: number;
94
+ readonly memo?: string;
95
+ readonly recentBlockhash?: string;
96
+ readonly lastValidBlockHeight?: string;
97
+ }
98
+ /**
99
+ * Build one `accepts[]` entry — the resource-server side of a 402 response.
100
+ * Pure data; does not touch chain, does not fetch a blockhash (pass
101
+ * `recentBlockhash`/`lastValidBlockHeight` if you have one fresh, or omit
102
+ * and let the buyer fetch their own — the spec allows either).
103
+ */
104
+ export declare function buildX402Requirements(p: BuildX402RequirementsParams): X402PaymentRequirements;
105
+ /** Parse a 402 response body, returning null if it doesn't look like one
106
+ * (missing/wrong `x402Version`, `accepts` not an array). Never throws. */
107
+ export declare function parseX402Response(body: unknown): X402PaymentRequiredResponse | null;
package/dist/x402.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * x402 ("HTTP 402 Payment Required") payment requirements — the pure,
3
+ * dependency-free half of x1id's x402 support. Types and the
4
+ * `PaymentRequirements` builder live here; actual transaction construction
5
+ * and verification need real Solana transaction parsing (`@solana/web3.js`)
6
+ * and live in `mcp/src/x402/` instead, matching this SDK's established
7
+ * boundary (zero dependencies, no crypto/serialization — see delegate.ts's
8
+ * module docs for the same reasoning applied elsewhere).
9
+ *
10
+ * Field names and the `"exact"` scheme's wire shape are taken verbatim from
11
+ * the x402 Foundation's own SVM scheme spec
12
+ * (x402-foundation/x402, specs/schemes/exact/scheme_exact_svm.md, and the
13
+ * Go reference implementation under go/mechanisms/svm/exact/), verified
14
+ * 2026-09-22 — NOT invented. See `docs/x402.md` for the full design,
15
+ * including what's explicitly OUT of scope here (a facilitator).
16
+ *
17
+ * # The one x1id-specific choice: the network identifier
18
+ *
19
+ * x402's Solana networks use `"solana:<genesis-hash>"` (CAIP-2-shaped). X1
20
+ * is SVM-compatible but its OWN chain — not Solana — so it gets its own
21
+ * namespace under the identical convention: `"x1:<genesis-hash>"`. This is
22
+ * NOT an x402-foundation-registered network; it's a principled extension of
23
+ * their own pattern to a new SVM-compatible chain, not a claim of official
24
+ * support. {@link X1_TESTNET_NETWORK} is the live testnet genesis hash,
25
+ * verified against `getGenesisHash` 2026-09-22.
26
+ */
27
+ /** X1 testnet's x402 network identifier (`"x1:<genesis-hash>"`, verified
28
+ * live 2026-09-22 via `getGenesisHash`). */
29
+ export const X1_TESTNET_NETWORK = "x1:C7ucgdDEhxLTpXHhWSZxavSVmaNTUJWwT5iTdeaviDho";
30
+ /** The x402 scheme x1id implements — "exact": pay a fixed amount of a named
31
+ * asset to a named recipient. (x402 also defines "upto" for usage-based
32
+ * pricing; not implemented here.) */
33
+ export const X402_SCHEME_EXACT = "exact";
34
+ export const X402_VERSION = 2;
35
+ /**
36
+ * Build one `accepts[]` entry — the resource-server side of a 402 response.
37
+ * Pure data; does not touch chain, does not fetch a blockhash (pass
38
+ * `recentBlockhash`/`lastValidBlockHeight` if you have one fresh, or omit
39
+ * and let the buyer fetch their own — the spec allows either).
40
+ */
41
+ export function buildX402Requirements(p) {
42
+ if (!/^\d+$/.test(p.amount)) {
43
+ throw new Error("amount must be a decimal string of smallest units (e.g. \"1000000\")");
44
+ }
45
+ if (p.memo !== undefined && new TextEncoder().encode(p.memo).length > 256) {
46
+ throw new Error("memo must be <=256 UTF-8 bytes");
47
+ }
48
+ const extra = { feePayer: p.buyer };
49
+ if (p.memo !== undefined)
50
+ extra.memo = p.memo;
51
+ if (p.recentBlockhash !== undefined) {
52
+ extra.recentBlockhash = p.recentBlockhash;
53
+ }
54
+ if (p.lastValidBlockHeight !== undefined) {
55
+ extra.lastValidBlockHeight = p.lastValidBlockHeight;
56
+ }
57
+ return {
58
+ scheme: X402_SCHEME_EXACT,
59
+ network: p.network ?? X1_TESTNET_NETWORK,
60
+ amount: p.amount,
61
+ asset: p.asset,
62
+ payTo: p.payTo,
63
+ maxTimeoutSeconds: p.maxTimeoutSeconds ?? 60,
64
+ extra,
65
+ };
66
+ }
67
+ /** Parse a 402 response body, returning null if it doesn't look like one
68
+ * (missing/wrong `x402Version`, `accepts` not an array). Never throws. */
69
+ export function parseX402Response(body) {
70
+ if (typeof body !== "object" || body === null)
71
+ return null;
72
+ const b = body;
73
+ if (b.x402Version !== X402_VERSION || !Array.isArray(b.accepts))
74
+ return null;
75
+ return { x402Version: X402_VERSION, accepts: b.accepts };
76
+ }
package/package.json CHANGED
@@ -1,34 +1,70 @@
1
1
  {
2
2
  "name": "@x1id/resolve",
3
- "version": "0.3.0",
3
+ "version": "0.7.0",
4
4
  "description": "Resolve @handles and X1NS names on X1. Never silently picks between namespaces.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
- ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
10
13
  "./wasm/x1_resolve_wasm.wasm": "./wasm/x1_resolve_wasm.wasm",
11
14
  "./wasm/*": "./wasm/*",
12
15
  "./package.json": "./package.json"
13
16
  },
14
- "files": ["dist", "wasm", "README.md"],
17
+ "files": [
18
+ "dist",
19
+ "wasm",
20
+ "schema",
21
+ "README.md"
22
+ ],
15
23
  "scripts": {
16
24
  "build:wasm": "cargo build --release -p x1-resolve-wasm --target wasm32-unknown-unknown && cp target/wasm32-unknown-unknown/release/x1_resolve_wasm.wasm wasm/",
17
25
  "build": "tsc -p tsconfig.json",
18
26
  "test": "node --test test/*.test.js",
19
27
  "lint:package": "publint",
20
- "prepublishOnly": "cargo build --release -p x1-resolve-wasm --target wasm32-unknown-unknown --manifest-path ../Cargo.toml && node ../web/verify-wasm.mjs wasm/x1_resolve_wasm.wasm ../target/wasm32-unknown-unknown/release/x1_resolve_wasm.wasm && npm run build"
28
+ "prepublishOnly": "npm run build"
21
29
  },
22
- "keywords": ["x1", "x1id", "solana", "svm", "naming", "x1ns", "handles", "wallet", "resolver", "web3"],
30
+ "keywords": [
31
+ "x1",
32
+ "x1id",
33
+ "solana",
34
+ "svm",
35
+ "naming",
36
+ "x1ns",
37
+ "handles",
38
+ "wallet",
39
+ "resolver",
40
+ "web3"
41
+ ],
23
42
  "homepage": "https://x1id.io",
24
43
  "license": "MIT",
25
- "publishConfig": { "access": "public" },
26
- "engines": { "node": ">=18" },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
27
50
  "devDependencies": {
28
51
  "typescript": "^5.6.0",
29
52
  "@types/node": "^22.0.0",
30
53
  "publint": "^0.2.0"
31
54
  },
32
- "peerDependencies": { "@solana/web3.js": "^1.95.0" },
33
- "peerDependenciesMeta": { "@solana/web3.js": { "optional": true } }
55
+ "peerDependencies": {
56
+ "@solana/web3.js": "^1.95.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@solana/web3.js": {
60
+ "optional": true
61
+ }
62
+ },
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/fortiblox/x1id-sdk.git"
66
+ },
67
+ "bugs": {
68
+ "url": "https://x1id.io"
69
+ }
34
70
  }
@@ -0,0 +1,91 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://x1id.io/schema/agent-manifest.json",
4
+ "title": "x1id agent manifest",
5
+ "description": "A machine-readable description of an @handle operating as an AI agent — what it does, how to reach it, and how to pay it. Nothing here is a payment fact: payment.addresses[] is a mirror of the on-chain address Record(s) and payment.x402 is a pre-flight hint; the chain and the live 402 response always win. See docs/agent-records.md.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["x1id"],
9
+ "properties": {
10
+ "x1id": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "required": ["version", "name", "network", "handle"],
14
+ "properties": {
15
+ "version": { "const": 1 },
16
+ "name": { "type": "string", "minLength": 1, "maxLength": 260, "description": "The canonical @handle name, without the @." },
17
+ "network": { "type": "string", "enum": ["testnet", "mainnet"] },
18
+ "handle": { "type": "string", "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$", "description": "The Handle PDA account address, base58 — the uniqueness anchor. A manifest served for the wrong name/handle is discarded on read." }
19
+ }
20
+ },
21
+ "identity": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "properties": {
25
+ "attestation": { "type": "string", "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$", "description": "Base58 address of a non-stale Attestation account on this handle (kind DNS or SOCIAL). See docs/agent-records.md §4 for what L1 does and does not prove." },
26
+ "description": { "type": "string", "maxLength": 2048 }
27
+ }
28
+ },
29
+ "endpoints": {
30
+ "type": "array",
31
+ "maxItems": 32,
32
+ "items": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "required": ["type", "url"],
36
+ "properties": {
37
+ "type": { "type": "string", "enum": ["x402", "mcp", "a2a", "http"] },
38
+ "url": { "$ref": "#/$defs/HttpsUrl" }
39
+ }
40
+ }
41
+ },
42
+ "capabilities": {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "required": ["url"],
46
+ "properties": {
47
+ "schema": { "$ref": "#/$defs/HttpsUrl" },
48
+ "url": { "$ref": "#/$defs/HttpsUrl" }
49
+ }
50
+ },
51
+ "payment": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "properties": {
55
+ "addresses": {
56
+ "type": "array",
57
+ "maxItems": 16,
58
+ "items": {
59
+ "type": "object",
60
+ "additionalProperties": false,
61
+ "required": ["coinType", "address"],
62
+ "properties": {
63
+ "coinType": { "type": "integer", "minimum": 0 },
64
+ "address": { "type": "string", "minLength": 1, "maxLength": 128 },
65
+ "verified": { "type": "boolean" }
66
+ }
67
+ }
68
+ },
69
+ "x402": {
70
+ "type": "object",
71
+ "additionalProperties": false,
72
+ "required": ["network"],
73
+ "properties": {
74
+ "network": { "type": "string", "minLength": 1, "description": "e.g. \"x1-testnet\" or \"x1-mainnet\" — an x1id-defined network tag, not a CAIP-2 EVM chain id." },
75
+ "asset": { "type": "string", "maxLength": 128 },
76
+ "scheme": { "type": "string", "minLength": 1, "maxLength": 32 },
77
+ "facilitator": { "$ref": "#/$defs/HttpsUrl" },
78
+ "extra": { "type": "object" }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ },
84
+ "$defs": {
85
+ "HttpsUrl": {
86
+ "type": "string",
87
+ "pattern": "^https://[^\\s@/]+(/[^\\s]*)?$",
88
+ "maxLength": 2048
89
+ }
90
+ }
91
+ }
Binary file