@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/LICENSE +21 -0
- package/README.md +128 -1
- package/dist/accounts.d.ts +7 -0
- package/dist/accounts.js +8 -1
- package/dist/agent.d.ts +186 -0
- package/dist/agent.js +213 -0
- package/dist/attestation.d.ts +20 -8
- package/dist/attestation.js +22 -10
- package/dist/clearRecords.d.ts +60 -0
- package/dist/clearRecords.js +69 -0
- package/dist/commitReveal.d.ts +187 -0
- package/dist/commitReveal.js +245 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +17 -2
- package/dist/pnftTransfer.d.ts +151 -0
- package/dist/pnftTransfer.js +183 -0
- package/dist/recordWrite.d.ts +136 -0
- package/dist/recordWrite.js +228 -0
- package/dist/records.d.ts +24 -7
- package/dist/records.js +26 -9
- package/dist/register.d.ts +119 -0
- package/dist/register.js +183 -0
- package/dist/subname.d.ts +5 -0
- package/dist/subname.js +6 -1
- package/dist/textRecords.d.ts +18 -7
- package/dist/textRecords.js +20 -9
- package/dist/wasm.d.ts +24 -0
- package/dist/wasm.js +45 -0
- package/dist/x402.d.ts +107 -0
- package/dist/x402.js +76 -0
- package/package.json +45 -9
- package/schema/agent-manifest.json +91 -0
- package/wasm/x1_resolve_wasm.wasm +0 -0
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
|
package/dist/textRecords.d.ts
CHANGED
|
@@ -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`.
|
|
144
|
-
*
|
|
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.
|
|
166
|
-
*
|
|
167
|
-
*
|
|
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 {
|
package/dist/textRecords.js
CHANGED
|
@@ -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`.
|
|
168
|
-
*
|
|
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.
|
|
229
|
-
*
|
|
230
|
-
*
|
|
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
|
+
"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
|
-
".": {
|
|
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": [
|
|
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": "
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
21
29
|
},
|
|
22
|
-
"keywords": [
|
|
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": {
|
|
26
|
-
|
|
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": {
|
|
33
|
-
|
|
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
|