@x1id/resolve 0.1.0 → 0.2.1
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/README.md +156 -33
- package/dist/index.d.ts +5 -2
- package/dist/index.js +222 -25
- package/dist/types.d.ts +12 -1
- package/dist/types.js +3 -1
- package/dist/wasm.d.ts +15 -0
- package/dist/wasm.js +29 -0
- package/package.json +41 -10
- package/wasm/x1_resolve_wasm.wasm +0 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ answered, and a mixed shape is refused rather than guessed.
|
|
|
12
12
|
import { createResolver, WasmResolver } from "@x1id/resolve";
|
|
13
13
|
|
|
14
14
|
const wasm = await WasmResolver.fromBytes(/* the module bytes, see below */);
|
|
15
|
-
const x1id = createResolver({ rpcUrl: "https://rpc.
|
|
15
|
+
const x1id = createResolver({ rpcUrl: "https://rpc.testnet.x1.xyz", wasm });
|
|
16
16
|
|
|
17
17
|
const r = await x1id.resolve("@jack");
|
|
18
18
|
// { name: "jack", namespace: "handle", address: "H8Fs…", chain: "X1",
|
|
@@ -20,28 +20,28 @@ const r = await x1id.resolve("@jack");
|
|
|
20
20
|
|
|
21
21
|
await x1id.resolve("jack.x1"); // resolves the X1NS domain instead
|
|
22
22
|
await x1id.resolve("@jack.x1"); // throws ResolveError { code: "ambiguous" }
|
|
23
|
+
await x1id.reverse("H8Fs…"); // "nike" — the address's primary @handle, or null
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
> **Which RPC?** The `@handle` registry is deployed on **X1 testnet** today
|
|
27
|
+
> (`https://rpc.testnet.x1.xyz`). X1NS domains live on **mainnet**
|
|
28
|
+
> (`https://rpc.mainnet.x1.xyz`). Point the resolver at the chain that holds
|
|
29
|
+
> the names you want; on an RPC where the registry is not deployed a handle is
|
|
30
|
+
> an honest `not-found`, never a fabricated address.
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
A wallet that resolves through a third party's uptime **breaks the day that
|
|
29
|
-
party loses interest**, and every pending payment breaks with it. This SDK never
|
|
30
|
-
calls `api.x1ns.xyz` or any of ours: it **derives accounts and reads them
|
|
31
|
-
straight off chain over RPC**. Point it at any X1 RPC and it works, ours up or
|
|
32
|
-
not.
|
|
32
|
+
## Where this code lives
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
This directory (`sdk/` in the `x1-handles` monorepo) **is the source of
|
|
35
|
+
truth**, published to npm as **`@x1id/resolve`** (MIT). The X1ID app vendors a
|
|
36
|
+
byte-identical copy at `app/src/lib/x1/x1sdk/` (plus the WASM at
|
|
37
|
+
`app/public/x1_resolve_wasm.wasm`) — that copy is a **duplicate, not a
|
|
38
|
+
source**: any change here must be mirrored into `app/src/lib/x1/x1sdk`
|
|
39
|
+
(`test/sync.test.js` fails inside the monorepo when they drift).
|
|
38
40
|
|
|
39
41
|
## Install
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
44
|
npm install @x1id/resolve
|
|
43
|
-
# or straight from source:
|
|
44
|
-
npm install github:fortiblox/x1id-sdk
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
`@solana/web3.js` is an **optional** peer dependency — you only need it if you
|
|
@@ -50,9 +50,10 @@ pass web3.js types around; the SDK itself talks to RPC directly.
|
|
|
50
50
|
## Loading the WASM module
|
|
51
51
|
|
|
52
52
|
Derivation needs the WASM module. The package ships it at
|
|
53
|
-
`@x1id/resolve/wasm/x1_resolve_wasm.wasm
|
|
53
|
+
`@x1id/resolve/wasm/x1_resolve_wasm.wasm` (an exported subpath, so both
|
|
54
|
+
`require.resolve` and bundler asset imports find it).
|
|
54
55
|
|
|
55
|
-
**Node**
|
|
56
|
+
**Node (ESM)**
|
|
56
57
|
|
|
57
58
|
```ts
|
|
58
59
|
import { readFileSync } from "node:fs";
|
|
@@ -63,16 +64,30 @@ const require = createRequire(import.meta.url);
|
|
|
63
64
|
const wasmPath = require.resolve("@x1id/resolve/wasm/x1_resolve_wasm.wasm");
|
|
64
65
|
const wasm = await WasmResolver.fromBytes(readFileSync(wasmPath));
|
|
65
66
|
|
|
66
|
-
const x1id = createResolver({ rpcUrl: "https://rpc.
|
|
67
|
+
const x1id = createResolver({ rpcUrl: "https://rpc.testnet.x1.xyz", wasm });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Node ≥ 20.6 can use `import.meta.resolve` instead of `createRequire`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { fileURLToPath } from "node:url";
|
|
74
|
+
const wasmPath = fileURLToPath(import.meta.resolve("@x1id/resolve/wasm/x1_resolve_wasm.wasm"));
|
|
67
75
|
```
|
|
68
76
|
|
|
69
77
|
**Browser / bundler**
|
|
70
78
|
|
|
71
79
|
```ts
|
|
72
|
-
|
|
80
|
+
// Vite / Rollup / webpack 5 — the `?url` suffix hands you the asset URL
|
|
81
|
+
import wasmUrl from "@x1id/resolve/wasm/x1_resolve_wasm.wasm?url";
|
|
82
|
+
import { WasmResolver } from "@x1id/resolve";
|
|
83
|
+
|
|
73
84
|
const wasm = await WasmResolver.fromBytes(await (await fetch(wasmUrl)).arrayBuffer());
|
|
74
85
|
```
|
|
75
86
|
|
|
87
|
+
Or copy `node_modules/@x1id/resolve/wasm/x1_resolve_wasm.wasm` into your static
|
|
88
|
+
directory and `fetch("/x1_resolve_wasm.wasm")` it — that is what
|
|
89
|
+
[x1id.io](https://x1id.io) does.
|
|
90
|
+
|
|
76
91
|
## The one rule: show the namespace before you send
|
|
77
92
|
|
|
78
93
|
`@jack` and `jack.x1` can resolve to **different owners**. There is no API in
|
|
@@ -96,15 +111,53 @@ try {
|
|
|
96
111
|
}
|
|
97
112
|
```
|
|
98
113
|
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
Dispatch is on shape (`@` prefix vs known TLD suffix), never "try one, fall back
|
|
115
|
+
to the other". `@jack.x1` throws `ambiguous` rather than guessing. Use
|
|
116
|
+
`looksLikeName(input)` to decide whether to attempt resolution at all, so
|
|
117
|
+
pasting base58 does not surface a validation error.
|
|
118
|
+
|
|
119
|
+
## Reads chain state, not an API
|
|
120
|
+
|
|
121
|
+
X1NS names resolve by deriving accounts and reading them over RPC. This library
|
|
122
|
+
never calls `api.x1ns.xyz` or any API of ours — a wallet that resolves through
|
|
123
|
+
a third party's uptime breaks when they lose interest. `@handle` resolution
|
|
124
|
+
derives the PDA (`["handle", canonical]` under `handleProgramId`) and reads the
|
|
125
|
+
account directly: an untokenized handle resolves to its `owner`; a tokenized
|
|
126
|
+
one (NFT extension present) resolves to whoever currently holds the handle's
|
|
127
|
+
NFT — the `owner` field is informational once a name is tokenized, and the SDK
|
|
128
|
+
never falls back to it. Reverse lookup reads the registry's on-chain `Primary`
|
|
129
|
+
pointer (`["primary", owner]`). All reads use `confirmed` commitment.
|
|
130
|
+
|
|
131
|
+
Address derivation uses a small Rust→WASM module (a real ed25519 on-curve check
|
|
132
|
+
— the kind of thing that is subtly wrong for months when hand-rolled in JS, and
|
|
133
|
+
this is the kind of code that loses money when it is wrong). It is verified
|
|
134
|
+
against live accounts in `test/wasm.test.js`.
|
|
101
135
|
|
|
102
136
|
## Reverse lookup
|
|
103
137
|
|
|
104
138
|
```ts
|
|
105
|
-
const name = await x1id.reverse("H8Fs…");
|
|
139
|
+
const name = await x1id.reverse("H8Fs…");
|
|
140
|
+
// "nike" → the address's primary @handle (canonical, no "@")
|
|
141
|
+
// "5WL7…dMKK.x1" → no @handle primary; X1NS primary-domain record
|
|
142
|
+
// (account + TLD — X1NS stores no label on chain)
|
|
143
|
+
// null → no primary anywhere
|
|
106
144
|
```
|
|
107
145
|
|
|
146
|
+
A `@handle` primary is only returned when it is still trustworthy — the same
|
|
147
|
+
rule the X1ID API and app apply:
|
|
148
|
+
|
|
149
|
+
1. the `Primary` pointer and the handle it names both exist and are owned by
|
|
150
|
+
the registry program;
|
|
151
|
+
2. `set_at >= handle.registered_at` — a pointer set before the name's current
|
|
152
|
+
registration belongs to a previous owner of that name;
|
|
153
|
+
3. the address still holds authority: an untokenized handle's `owner` is the
|
|
154
|
+
address, or — when the handle is tokenized — the address's associated token
|
|
155
|
+
account holds exactly 1 of the handle's NFT.
|
|
156
|
+
|
|
157
|
+
A pointer that fails any rule is treated as absent (falls through to X1NS, then
|
|
158
|
+
`null`), never as an error. Only a malformed *address* throws
|
|
159
|
+
(`ResolveError { code: "unrecognized" }`).
|
|
160
|
+
|
|
108
161
|
## Multi-chain
|
|
109
162
|
|
|
110
163
|
A handle can carry addresses for several chains. Pass the one you want:
|
|
@@ -124,22 +177,52 @@ a wrong address on the wrong chain.
|
|
|
124
177
|
|
|
125
178
|
| `verification` | meaning |
|
|
126
179
|
|---|---|
|
|
127
|
-
| `verified` | control of the address was proved
|
|
128
|
-
| `unverified` |
|
|
180
|
+
| `verified` | control of the address was proved: the address owns the `@handle` registry account, or — for a tokenized handle — holds its NFT **in its associated token account** (the only place the registry program recognises as the name's authority, same rule as `reverse()`). |
|
|
181
|
+
| `unverified` | ownership was not proved for that chain (X1NS), or — for a tokenized handle — the NFT is held outside the holder's ATA (an auxiliary account or an escrow): that wallet controls the token but cannot act for the name until it is back in its ATA. **Show a warning.** |
|
|
182
|
+
|
|
183
|
+
A tokenized handle whose NFT has been **burned** (mint supply 0) resolves to no
|
|
184
|
+
one: `ResolveError { code: "not-found", reason: "nft-burned" }`. The registry
|
|
185
|
+
account's stale `owner` field is never returned for a tokenized handle, on any
|
|
186
|
+
path.
|
|
129
187
|
|
|
130
188
|
## What works today
|
|
131
189
|
|
|
132
190
|
| | Status |
|
|
133
191
|
|---|---|
|
|
134
192
|
| X1NS resolution (`.x1/.xnt/.xen`) | ✅ mainnet |
|
|
135
|
-
| `@handle` resolution | ✅ on any RPC where the registry is deployed
|
|
136
|
-
| Reverse lookup | ✅ |
|
|
193
|
+
| `@handle` resolution | ✅ on any RPC where the registry is deployed — **X1 testnet today**; mainnet on deploy |
|
|
194
|
+
| Reverse lookup | ✅ on-chain `@handle` primary (testnet today) with X1NS primary-domain fallback (mainnet); X1NS results are `<domainAccount>.<tld>`, not a label |
|
|
137
195
|
| Per-chain ETH/BTC records | 🔜 roadmap |
|
|
138
|
-
| Registration / transfer (write) | 🔜 roadmap |
|
|
196
|
+
| Registration / transfer / `set_primary` (write) | 🔜 roadmap — use [x1id.io](https://x1id.io) |
|
|
139
197
|
|
|
140
198
|
The `@handle` registry program id is configurable (`handleProgramId`) and
|
|
141
|
-
defaults to the canonical X1 deployment
|
|
142
|
-
|
|
199
|
+
defaults to the canonical X1 deployment
|
|
200
|
+
(`8JgnNWi24bq9uzfnT9XmkWxvaWMVgoEs9bu8QsHhLe1P`).
|
|
201
|
+
|
|
202
|
+
## Errors
|
|
203
|
+
|
|
204
|
+
`ResolveError` carries a `code` a UI can branch on:
|
|
205
|
+
|
|
206
|
+
| code | meaning |
|
|
207
|
+
|---|---|
|
|
208
|
+
| `unrecognized` | not a handle or known domain — probably a raw address |
|
|
209
|
+
| `ambiguous` | both shapes at once, e.g. `@jack.x1` |
|
|
210
|
+
| `invalid-handle` / `invalid-domain` | right shape, invalid content |
|
|
211
|
+
| `not-found` | valid name, not registered — or `reason: "nft-burned"`: tokenized and its NFT was burned, so no one holds it |
|
|
212
|
+
| `no-record-for-chain` | registered, but no address for the requested chain |
|
|
213
|
+
| `rpc-error` | transport failure |
|
|
214
|
+
|
|
215
|
+
## Normalization
|
|
216
|
+
|
|
217
|
+
`normalizeHandle` mirrors the Rust `handle-normalize` crate, which is the source
|
|
218
|
+
of truth — the on-chain registry derives PDA seeds from it. A conformance suite
|
|
219
|
+
runs both against generated fixtures in CI; divergence fails the build, because
|
|
220
|
+
a divergence would make handles registered under one normalization unreachable
|
|
221
|
+
under the other.
|
|
222
|
+
|
|
223
|
+
ASCII only, no Unicode or emoji. X1NS permits both; for a payment identifier
|
|
224
|
+
that is a homograph vector (`@аlice` with a Cyrillic `а` renders identically to
|
|
225
|
+
`@alice`).
|
|
143
226
|
|
|
144
227
|
## API
|
|
145
228
|
|
|
@@ -147,10 +230,10 @@ get an honest `not-found`, never a fabricated address.
|
|
|
147
230
|
createResolver(config: ResolverConfig): Resolver
|
|
148
231
|
|
|
149
232
|
interface ResolverConfig {
|
|
150
|
-
rpcUrl: string; // any X1 RPC
|
|
233
|
+
rpcUrl: string; // any X1 RPC (testnet for @handles today)
|
|
151
234
|
wasm: WasmResolver; // required — derivation runs in WASM, no JS fallback
|
|
152
235
|
fetchImpl?: typeof fetch; // custom transport (tests, proxies)
|
|
153
|
-
cacheTtlMs?: number; // default 30_000; 0 disables
|
|
236
|
+
cacheTtlMs?: number; // default 30_000; 0 disables (resolve() only)
|
|
154
237
|
handleProgramId?: string; // default: canonical X1 deployment
|
|
155
238
|
}
|
|
156
239
|
|
|
@@ -159,15 +242,55 @@ interface Resolver {
|
|
|
159
242
|
reverse(address: string): Promise<string | null>;
|
|
160
243
|
clearCache(): void;
|
|
161
244
|
}
|
|
245
|
+
|
|
246
|
+
class WasmResolver {
|
|
247
|
+
static fromBytes(bytes: BufferSource): Promise<WasmResolver>;
|
|
248
|
+
normalizeHandle(raw: string): string | null;
|
|
249
|
+
deriveX1nsAccount(label: string, tld: "x1" | "xnt" | "xen"): Uint8Array | null;
|
|
250
|
+
derivePrimaryAccount(owner: Uint8Array): Uint8Array | null; // X1NS primary-domain record
|
|
251
|
+
deriveHandleAccount(canonical: string, programId: Uint8Array): Uint8Array | null; // ["handle", name]
|
|
252
|
+
deriveHandlePrimaryAccount(owner: Uint8Array, programId: Uint8Array): Uint8Array | null; // ["primary", owner]
|
|
253
|
+
deriveAssociatedTokenAccount(owner: Uint8Array, mint: Uint8Array): Uint8Array | null;
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Also exported: `parseName`, `looksLikeName`, `normalizeHandle`, `namespaceLabel`,
|
|
258
|
+
`encodeBase58`, `decodeBase58_32`, `ResolveError`, `CHAIN_COIN_TYPE` and the
|
|
259
|
+
types `Resolved`, `Namespace`, `Chain`, `Verification`, `ResolveErrorCode`.
|
|
260
|
+
|
|
261
|
+
## Developing
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
npm run build:wasm # cargo build → wasm/x1_resolve_wasm.wasm (needs the wasm32-unknown-unknown target)
|
|
265
|
+
npm run build # tsc → dist/
|
|
266
|
+
npm test # node --test; X1_LIVE=1 also runs the testnet reverse() case
|
|
162
267
|
```
|
|
163
268
|
|
|
164
|
-
`
|
|
165
|
-
`
|
|
269
|
+
`wasm/x1_resolve_wasm.wasm` is committed and shipped. Rebuild it whenever
|
|
270
|
+
`crates/x1-resolve-wasm` (or its dependencies) change. A rebuild is **not**
|
|
271
|
+
guaranteed byte-identical: the module published in 0.2.0 (48,765 bytes) differs
|
|
272
|
+
from the monorepo/app copy (48,742 bytes) by 23 bytes — same 13 exports,
|
|
273
|
+
functionally equivalent on every derivation tested (handle PDA, X1NS account,
|
|
274
|
+
primary PDA, ATA, normalize; `test/wasm.test.js` checks them against live
|
|
275
|
+
accounts). Treat the committed binary as the artefact of record and verify by
|
|
276
|
+
running the tests, not by comparing hashes.
|
|
277
|
+
|
|
278
|
+
## Publishing
|
|
279
|
+
|
|
280
|
+
Published from the (private) release mirror, not from the monorepo: sync this
|
|
281
|
+
directory (plus `crates/`) there, then push a `v<version>` tag that matches
|
|
282
|
+
`package.json` — the release workflow builds the WASM, runs the tests and
|
|
283
|
+
`publint`, and runs `npm publish`. There is **no provenance attestation** on
|
|
284
|
+
the published package: npm only accepts provenance from public source
|
|
285
|
+
repositories, and the mirror is private. (The README bundled inside 0.2.0
|
|
286
|
+
mentions `--provenance`; that was aspirational — no attestation was ever
|
|
287
|
+
attached, and this file is the accurate statement.)
|
|
166
288
|
|
|
167
289
|
## Links
|
|
168
290
|
|
|
169
291
|
- Home & docs: **[x1id.io](https://x1id.io)**
|
|
170
|
-
-
|
|
292
|
+
- Developer docs: **[docs.fortiblox.com/docs/x1id](https://docs.fortiblox.com/docs/x1id)**
|
|
293
|
+
- Questions & issues: [x1id.io](https://x1id.io) — contact links in the footer
|
|
171
294
|
|
|
172
295
|
## License
|
|
173
296
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Resolve `@handles` and X1NS names on X1.
|
|
3
3
|
*
|
|
4
4
|
* ```ts
|
|
5
|
-
* import { createResolver } from "@x1id/resolve";
|
|
5
|
+
* import { createResolver, WasmResolver } from "@x1id/resolve";
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* // bytes of @x1id/resolve/wasm/x1_resolve_wasm.wasm — see the README for loading
|
|
8
|
+
* const wasm = await WasmResolver.fromBytes(wasmBytes);
|
|
9
|
+
* // The @handle registry is on X1 testnet today; X1NS domains are on mainnet.
|
|
10
|
+
* const r = createResolver({ rpcUrl: "https://rpc.testnet.x1.xyz", wasm });
|
|
8
11
|
* const res = await r.resolve("@jack", { chain: "X1" });
|
|
9
12
|
* // { name: "jack", namespace: "handle", address: "...", verification: "verified" }
|
|
10
13
|
* ```
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Resolve `@handles` and X1NS names on X1.
|
|
3
3
|
*
|
|
4
4
|
* ```ts
|
|
5
|
-
* import { createResolver } from "@x1id/resolve";
|
|
5
|
+
* import { createResolver, WasmResolver } from "@x1id/resolve";
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* // bytes of @x1id/resolve/wasm/x1_resolve_wasm.wasm — see the README for loading
|
|
8
|
+
* const wasm = await WasmResolver.fromBytes(wasmBytes);
|
|
9
|
+
* // The @handle registry is on X1 testnet today; X1NS domains are on mainnet.
|
|
10
|
+
* const r = createResolver({ rpcUrl: "https://rpc.testnet.x1.xyz", wasm });
|
|
8
11
|
* const res = await r.resolve("@jack", { chain: "X1" });
|
|
9
12
|
* // { name: "jack", namespace: "handle", address: "...", verification: "verified" }
|
|
10
13
|
* ```
|
|
@@ -43,9 +46,80 @@ const SPL_NAME_HEADER_LEN = 96;
|
|
|
43
46
|
const DEFAULT_HANDLE_PROGRAM = "8JgnNWi24bq9uzfnT9XmkWxvaWMVgoEs9bu8QsHhLe1P";
|
|
44
47
|
// Handle account layout, mirrored from the on-chain program:
|
|
45
48
|
// discriminator(8) name(32) name_len(1) owner(32) ...
|
|
46
|
-
//
|
|
49
|
+
// `owner` is the address an UNTOKENIZED handle resolves to. Once the handle
|
|
50
|
+
// is tokenized (NFT extension present, see `parseHandleAccount`) the program
|
|
51
|
+
// treats `owner` as informational only — authority is whoever holds the NFT —
|
|
52
|
+
// and so must every reader.
|
|
47
53
|
const HANDLE_OWNER_OFFSET = 41;
|
|
48
|
-
|
|
54
|
+
// `Handle`'s fixed base allocation (`space = 8 + INIT_SPACE`). The NFT
|
|
55
|
+
// extension, when present, is appended at this boundary regardless of the
|
|
56
|
+
// compact Borsh length of the (variable, `Option`-bearing) struct content —
|
|
57
|
+
// mirrors `Handle::NFT_EXT_OFFSET` in the program and `HANDLE_BASE_LEN` in
|
|
58
|
+
// tools/api.
|
|
59
|
+
const HANDLE_BASE_LEN = 8 + 149;
|
|
60
|
+
// `Primary` pointer account (`["primary", owner]` under the registry):
|
|
61
|
+
// disc(8) | owner(32) | handle(32) | set_at(i64 LE, 8) | bump(1) = 81 bytes
|
|
62
|
+
const PRIMARY_LEN = 81;
|
|
63
|
+
const PRIMARY_HANDLE_OFFSET = 40;
|
|
64
|
+
const PRIMARY_SET_AT_OFFSET = 72;
|
|
65
|
+
// SPL Token account: mint(32) | owner(32) | amount(u64 LE, 8) | ...
|
|
66
|
+
const TOKEN_ACCOUNT_MIN_LEN = 72;
|
|
67
|
+
// SPL Token mint: mint_authority COption(4+32) | supply(u64 LE, 8) @36 | ...
|
|
68
|
+
const MINT_SUPPLY_OFFSET = 36;
|
|
69
|
+
const MINT_MIN_LEN = MINT_SUPPLY_OFFSET + 8;
|
|
70
|
+
// The registry mints handle NFTs with the legacy SPL Token program, so every
|
|
71
|
+
// token account for a handle mint is owned by it.
|
|
72
|
+
const SPL_TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
73
|
+
function readI64(data, offset) {
|
|
74
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength).getBigInt64(offset, true);
|
|
75
|
+
}
|
|
76
|
+
function readU64(data, offset) {
|
|
77
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength).getBigUint64(offset, true);
|
|
78
|
+
}
|
|
79
|
+
function bytesEqual(a, b) {
|
|
80
|
+
if (a.length !== b.length)
|
|
81
|
+
return false;
|
|
82
|
+
for (let i = 0; i < a.length; i++)
|
|
83
|
+
if (a[i] !== b[i])
|
|
84
|
+
return false;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parse the `Handle` fields the reverse rule needs. Port of `parse_handle` in
|
|
89
|
+
* tools/api — sequential, because `recovery` / `recovery_target` are
|
|
90
|
+
* `Option<Pubkey>` (Borsh: tag byte, then 32 bytes when `Some`) and shift
|
|
91
|
+
* `registered_at`. The NFT mint is read at the fixed base boundary, not the
|
|
92
|
+
* sequential position.
|
|
93
|
+
*
|
|
94
|
+
* disc(8) name(32) name_len(1) owner(32) handle_type(1)
|
|
95
|
+
* recovery: Option<Pubkey> recovery_initiated_at: i64
|
|
96
|
+
* recovery_target: Option<Pubkey> registered_at: i64 bump(1)
|
|
97
|
+
* [at 8+149: nft tag(1) mint(32)]
|
|
98
|
+
*/
|
|
99
|
+
function parseHandleAccount(data) {
|
|
100
|
+
if (data.length < HANDLE_BASE_LEN)
|
|
101
|
+
return null;
|
|
102
|
+
const nameLen = Math.min(data[40], 32);
|
|
103
|
+
const name = new TextDecoder().decode(data.slice(8, 8 + nameLen));
|
|
104
|
+
const owner = data.slice(HANDLE_OWNER_OFFSET, HANDLE_OWNER_OFFSET + 32);
|
|
105
|
+
let pos = 73 + 1; // owner end + handle_type(1)
|
|
106
|
+
// recovery: Option<Pubkey>
|
|
107
|
+
if (pos >= data.length)
|
|
108
|
+
return null;
|
|
109
|
+
pos += 1 + (data[pos] === 1 ? 32 : 0);
|
|
110
|
+
pos += 8; // recovery_initiated_at
|
|
111
|
+
// recovery_target: Option<Pubkey>
|
|
112
|
+
if (pos >= data.length)
|
|
113
|
+
return null;
|
|
114
|
+
pos += 1 + (data[pos] === 1 ? 32 : 0);
|
|
115
|
+
if (pos + 8 > data.length)
|
|
116
|
+
return null;
|
|
117
|
+
const registeredAt = readI64(data, pos);
|
|
118
|
+
const nftMint = data.length >= HANDLE_BASE_LEN + 33 && data[HANDLE_BASE_LEN] === 1
|
|
119
|
+
? data.slice(HANDLE_BASE_LEN + 1, HANDLE_BASE_LEN + 33)
|
|
120
|
+
: null;
|
|
121
|
+
return { name, owner, registeredAt, nftMint };
|
|
122
|
+
}
|
|
49
123
|
export function createResolver(config) {
|
|
50
124
|
const ttl = config.cacheTtlMs ?? 30_000;
|
|
51
125
|
const cache = new Map();
|
|
@@ -60,6 +134,9 @@ export function createResolver(config) {
|
|
|
60
134
|
// Explicit type so the non-null narrowing survives into the resolveHandle
|
|
61
135
|
// closure below — TS widens a captured `const` back to its declared type.
|
|
62
136
|
const handleProgram = decodedProgram;
|
|
137
|
+
// Re-encoded (not the caller's string) so a non-canonical base58 spelling of
|
|
138
|
+
// the same key still compares equal to the RPC's `owner` field.
|
|
139
|
+
const programBase58 = encodeBase58(handleProgram);
|
|
63
140
|
async function rpc(method, params) {
|
|
64
141
|
let res;
|
|
65
142
|
try {
|
|
@@ -81,11 +158,12 @@ export function createResolver(config) {
|
|
|
81
158
|
}
|
|
82
159
|
return body.result;
|
|
83
160
|
}
|
|
84
|
-
/** Fetch raw account data, or null when the account
|
|
85
|
-
|
|
161
|
+
/** Fetch raw account data plus the owning program, or null when the account
|
|
162
|
+
* does not exist. */
|
|
163
|
+
async function accountInfo(address) {
|
|
86
164
|
const result = (await rpc("getAccountInfo", [
|
|
87
165
|
address,
|
|
88
|
-
{ encoding: "base64" },
|
|
166
|
+
{ encoding: "base64", commitment: "confirmed" },
|
|
89
167
|
]));
|
|
90
168
|
const value = result?.value;
|
|
91
169
|
if (!value)
|
|
@@ -95,7 +173,11 @@ export function createResolver(config) {
|
|
|
95
173
|
const out = new Uint8Array(bin.length);
|
|
96
174
|
for (let i = 0; i < bin.length; i++)
|
|
97
175
|
out[i] = bin.charCodeAt(i);
|
|
98
|
-
return out;
|
|
176
|
+
return { data: out, owner: value.owner };
|
|
177
|
+
}
|
|
178
|
+
/** Fetch raw account data, or null when the account does not exist. */
|
|
179
|
+
async function accountData(address) {
|
|
180
|
+
return (await accountInfo(address))?.data ?? null;
|
|
99
181
|
}
|
|
100
182
|
async function resolveX1ns(canonical, label, tld, chain, input) {
|
|
101
183
|
const account = config.wasm.deriveX1nsAccount(label, tld);
|
|
@@ -133,35 +215,85 @@ export function createResolver(config) {
|
|
|
133
215
|
verification: "unverified",
|
|
134
216
|
};
|
|
135
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Current holder of a tokenized handle: the owner of the single token
|
|
220
|
+
* account holding the handle's NFT (supply 1, decimals 0). Never falls back
|
|
221
|
+
* to `Handle.owner` — after the NFT changes hands that field names the
|
|
222
|
+
* previous owner, and paying it is the exact failure this SDK exists to
|
|
223
|
+
* prevent.
|
|
224
|
+
*
|
|
225
|
+
* Parity with the program's `require_current_authority` (and this
|
|
226
|
+
* resolver's `reverse()`): the program recognises the holder as the name's
|
|
227
|
+
* authority only when the NFT sits in the holder's **associated token
|
|
228
|
+
* account** for the mint. A holder whose NFT is parked elsewhere (an
|
|
229
|
+
* auxiliary account, a program escrow) still controls the token, so the
|
|
230
|
+
* address is returned — but as `unverified`, because the registry will not
|
|
231
|
+
* let that address act for the name until the NFT is back in its ATA.
|
|
232
|
+
*
|
|
233
|
+
* A burned NFT (mint supply 0) is `not-found` with reason `nft-burned`: the
|
|
234
|
+
* name has no holder, and no one — least of all the stale `owner` — may be
|
|
235
|
+
* paid for it. Any other failure to find the holder is an `rpc-error`.
|
|
236
|
+
*/
|
|
237
|
+
async function nftHolder(canonical, mint, input) {
|
|
238
|
+
const mintKey = encodeBase58(mint);
|
|
239
|
+
const largest = (await rpc("getTokenLargestAccounts", [
|
|
240
|
+
mintKey,
|
|
241
|
+
{ commitment: "confirmed" },
|
|
242
|
+
]));
|
|
243
|
+
const holders = (largest?.value ?? []).filter((a) => a.amount === "1");
|
|
244
|
+
if (holders.length !== 1) {
|
|
245
|
+
const m = await accountInfo(mintKey);
|
|
246
|
+
if (m &&
|
|
247
|
+
m.owner === SPL_TOKEN_PROGRAM &&
|
|
248
|
+
m.data.length >= MINT_MIN_LEN &&
|
|
249
|
+
readU64(m.data, MINT_SUPPLY_OFFSET) === 0n) {
|
|
250
|
+
throw new ResolveError("not-found", `@${canonical}'s NFT has been burned — the name has no holder`, input, "nft-burned");
|
|
251
|
+
}
|
|
252
|
+
throw new ResolveError("rpc-error", `@${canonical} is tokenized but its NFT has no single holder`, input);
|
|
253
|
+
}
|
|
254
|
+
const holdingAccount = holders[0].address;
|
|
255
|
+
const t = await accountInfo(holdingAccount);
|
|
256
|
+
if (!t ||
|
|
257
|
+
t.owner !== SPL_TOKEN_PROGRAM ||
|
|
258
|
+
t.data.length < TOKEN_ACCOUNT_MIN_LEN ||
|
|
259
|
+
!bytesEqual(t.data.slice(0, 32), mint) ||
|
|
260
|
+
readU64(t.data, 64) !== 1n) {
|
|
261
|
+
throw new ResolveError("rpc-error", `@${canonical} is tokenized but its NFT holder account is malformed`, input);
|
|
262
|
+
}
|
|
263
|
+
const holder = t.data.slice(32, 64);
|
|
264
|
+
const ata = config.wasm.deriveAssociatedTokenAccount(holder, mint);
|
|
265
|
+
const inAta = ata !== null && encodeBase58(ata) === holdingAccount;
|
|
266
|
+
return { address: encodeBase58(holder), verification: inAta ? "verified" : "unverified" };
|
|
267
|
+
}
|
|
136
268
|
async function resolveHandle(canonical, chain, input) {
|
|
137
269
|
const account = config.wasm.deriveHandleAccount(canonical, handleProgram);
|
|
138
270
|
if (!account) {
|
|
139
271
|
throw new ResolveError("invalid-handle", `"${input}" is not a valid handle`, input);
|
|
140
272
|
}
|
|
141
|
-
const
|
|
142
|
-
|
|
273
|
+
const h = await accountInfo(encodeBase58(account));
|
|
274
|
+
// An account at the PDA that the registry does not own is not a handle
|
|
275
|
+
// (anyone can fund an address into existence) — the name is unregistered.
|
|
276
|
+
if (!h || h.owner !== programBase58) {
|
|
143
277
|
throw new ResolveError("not-found", `@${canonical} is not registered`, input);
|
|
144
278
|
}
|
|
145
|
-
|
|
279
|
+
const handle = parseHandleAccount(h.data);
|
|
280
|
+
if (!handle) {
|
|
146
281
|
throw new ResolveError("rpc-error", `@${canonical} returned a malformed account`, input);
|
|
147
282
|
}
|
|
148
|
-
|
|
149
|
-
// The owner is an X1/SVM address. Per-chain records (ETH/BTC) live in
|
|
283
|
+
// The address is an X1/SVM address. Per-chain records (ETH/BTC) live in
|
|
150
284
|
// separate record accounts the resolver does not read yet, so a request for
|
|
151
285
|
// another chain is an explicit "no record" rather than a wrong address.
|
|
152
286
|
if (chain !== "X1" && chain !== "SOL") {
|
|
153
287
|
throw new ResolveError("no-record-for-chain", `@${canonical} has no ${chain} record`, input);
|
|
154
288
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
verification: "verified",
|
|
164
|
-
};
|
|
289
|
+
// Same authority rule as the program's `require_current_authority` and
|
|
290
|
+
// this resolver's `reverse()`: untokenized → `Handle.owner` (verified: it
|
|
291
|
+
// holds the registry account); tokenized → whoever holds the NFT right
|
|
292
|
+
// now, verified only when it sits in that wallet's ATA (see `nftHolder`).
|
|
293
|
+
const { address, verification } = handle.nftMint === null
|
|
294
|
+
? { address: encodeBase58(handle.owner), verification: "verified" }
|
|
295
|
+
: await nftHolder(canonical, handle.nftMint, input);
|
|
296
|
+
return { input, name: canonical, namespace: "handle", address, chain, verification };
|
|
165
297
|
}
|
|
166
298
|
async function resolve(input, opts) {
|
|
167
299
|
const chain = opts?.chain ?? "X1";
|
|
@@ -183,12 +315,77 @@ export function createResolver(config) {
|
|
|
183
315
|
cache.set(key, { value, expires: Date.now() + ttl });
|
|
184
316
|
return value;
|
|
185
317
|
}
|
|
186
|
-
/**
|
|
318
|
+
/**
|
|
319
|
+
* Reverse resolution through the `@handle` registry's on-chain `Primary`
|
|
320
|
+
* pointer. Returns the canonical handle name (no `@`), or null when the
|
|
321
|
+
* address has no pointer or the pointer is no longer trustworthy.
|
|
322
|
+
*
|
|
323
|
+
* The read-side rule — identical to `tools/api` `fn reverse`, which is the
|
|
324
|
+
* reference — trusts a pointer only if all three hold:
|
|
325
|
+
* 1. the handle account it names exists and is owned by the registry;
|
|
326
|
+
* 2. `set_at >= handle.registered_at` — a pointer set before the handle's
|
|
327
|
+
* current registration belongs to a previous owner of that name;
|
|
328
|
+
* 3. the address still holds authority: untokenized → `handle.owner ==
|
|
329
|
+
* address`; tokenized → ATA(address, mint) exists, its mint/owner match
|
|
330
|
+
* and its amount is exactly 1.
|
|
331
|
+
* A pointer that fails any rule is treated as absent, never as an error: it
|
|
332
|
+
* is a stale artefact the owner can `clear_primary`, not a malformed chain.
|
|
333
|
+
*/
|
|
334
|
+
async function reverseHandle(owner) {
|
|
335
|
+
const pointer = config.wasm.deriveHandlePrimaryAccount(owner, handleProgram);
|
|
336
|
+
if (!pointer)
|
|
337
|
+
return null;
|
|
338
|
+
const p = await accountInfo(encodeBase58(pointer));
|
|
339
|
+
if (!p || p.data.length < PRIMARY_LEN || p.owner !== programBase58)
|
|
340
|
+
return null;
|
|
341
|
+
const handleKey = encodeBase58(p.data.slice(PRIMARY_HANDLE_OFFSET, PRIMARY_HANDLE_OFFSET + 32));
|
|
342
|
+
const setAt = readI64(p.data, PRIMARY_SET_AT_OFFSET);
|
|
343
|
+
// rule 1: the handle is live and a registry account.
|
|
344
|
+
const h = await accountInfo(handleKey);
|
|
345
|
+
if (!h || h.owner !== programBase58)
|
|
346
|
+
return null;
|
|
347
|
+
const handle = parseHandleAccount(h.data);
|
|
348
|
+
if (!handle)
|
|
349
|
+
return null;
|
|
350
|
+
// rule 2: staleness — the pointer must be at least as new as the handle.
|
|
351
|
+
if (setAt < handle.registeredAt)
|
|
352
|
+
return null;
|
|
353
|
+
// rule 3: authority still matches.
|
|
354
|
+
if (handle.nftMint === null) {
|
|
355
|
+
if (!bytesEqual(handle.owner, owner))
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
const ata = config.wasm.deriveAssociatedTokenAccount(owner, handle.nftMint);
|
|
360
|
+
if (!ata)
|
|
361
|
+
return null;
|
|
362
|
+
const t = await accountInfo(encodeBase58(ata));
|
|
363
|
+
if (!t || t.data.length < TOKEN_ACCOUNT_MIN_LEN)
|
|
364
|
+
return null;
|
|
365
|
+
const mintOk = bytesEqual(t.data.slice(0, 32), handle.nftMint);
|
|
366
|
+
const ownerOk = bytesEqual(t.data.slice(32, 64), owner);
|
|
367
|
+
const amount = readU64(t.data, 64);
|
|
368
|
+
if (!mintOk || !ownerOk || amount !== 1n)
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
return handle.name;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Address to its primary name, or null when none is set.
|
|
375
|
+
*
|
|
376
|
+
* Two sources, in order: the `@handle` registry's on-chain `Primary` pointer
|
|
377
|
+
* (returned as the plain canonical handle, e.g. `"nike"`), then — only when
|
|
378
|
+
* there is no valid handle primary — X1NS's primary-domain record, returned
|
|
379
|
+
* as `"<domainAccount>.<tld>"` exactly as before. The handle path never
|
|
380
|
+
* throws for a stale or dangling pointer; it falls through to X1NS instead.
|
|
381
|
+
*/
|
|
187
382
|
async function reverse(address) {
|
|
188
|
-
const { decodeBase58_32 } = await import("./base58.js");
|
|
189
383
|
const owner = decodeBase58_32(address);
|
|
190
384
|
if (!owner)
|
|
191
385
|
throw new ResolveError("unrecognized", `"${address}" is not an address`, address);
|
|
386
|
+
const handleName = await reverseHandle(owner);
|
|
387
|
+
if (handleName !== null)
|
|
388
|
+
return handleName;
|
|
192
389
|
const record = config.wasm.derivePrimaryAccount(owner);
|
|
193
390
|
if (!record)
|
|
194
391
|
throw new ResolveError("unrecognized", "could not derive record", address);
|
package/dist/types.d.ts
CHANGED
|
@@ -36,8 +36,19 @@ export interface Resolved {
|
|
|
36
36
|
export type Chain = "X1" | "SOL" | "ETH" | "BTC";
|
|
37
37
|
export declare const CHAIN_COIN_TYPE: Readonly<Record<Chain, number>>;
|
|
38
38
|
export type ResolveErrorCode = "unrecognized" | "ambiguous" | "invalid-handle" | "invalid-domain" | "not-found" | "no-record-for-chain" | "rpc-error";
|
|
39
|
+
/**
|
|
40
|
+
* Finer-grained cause for a `ResolveError`, when the code alone is not
|
|
41
|
+
* enough for a UI to explain what happened.
|
|
42
|
+
*
|
|
43
|
+
* - `nft-burned`: the handle is tokenized and its NFT has been burned (mint
|
|
44
|
+
* supply 0), so nobody holds authority over the name any more. Surfaces
|
|
45
|
+
* as `not-found` — the name resolves to no one — never as the registry
|
|
46
|
+
* account's stale `owner` field.
|
|
47
|
+
*/
|
|
48
|
+
export type ResolveErrorReason = "nft-burned";
|
|
39
49
|
export declare class ResolveError extends Error {
|
|
40
50
|
readonly code: ResolveErrorCode;
|
|
41
51
|
readonly input?: string | undefined;
|
|
42
|
-
|
|
52
|
+
readonly reason?: ResolveErrorReason | undefined;
|
|
53
|
+
constructor(code: ResolveErrorCode, message: string, input?: string | undefined, reason?: ResolveErrorReason | undefined);
|
|
43
54
|
}
|
package/dist/types.js
CHANGED
|
@@ -16,10 +16,12 @@ export const CHAIN_COIN_TYPE = Object.freeze({
|
|
|
16
16
|
export class ResolveError extends Error {
|
|
17
17
|
code;
|
|
18
18
|
input;
|
|
19
|
-
|
|
19
|
+
reason;
|
|
20
|
+
constructor(code, message, input, reason) {
|
|
20
21
|
super(message);
|
|
21
22
|
this.code = code;
|
|
22
23
|
this.input = input;
|
|
24
|
+
this.reason = reason;
|
|
23
25
|
this.name = "ResolveError";
|
|
24
26
|
}
|
|
25
27
|
}
|
package/dist/wasm.d.ts
CHANGED
|
@@ -39,5 +39,20 @@ export declare class WasmResolver {
|
|
|
39
39
|
* program the caller names — never a program baked into this module.
|
|
40
40
|
*/
|
|
41
41
|
deriveHandleAccount(canonical: string, programId: Uint8Array): Uint8Array | null;
|
|
42
|
+
/**
|
|
43
|
+
* 32-byte `@handle` registry `Primary` pointer account for a 32-byte owner
|
|
44
|
+
* address under a given registry program id — seeds `["primary", owner]`.
|
|
45
|
+
* This is the on-chain `set_primary` / `clear_primary` PDA, NOT X1NS's
|
|
46
|
+
* primary-domain record (`derivePrimaryAccount`), which lives under the
|
|
47
|
+
* X1NS registrar and has a different layout.
|
|
48
|
+
*/
|
|
49
|
+
deriveHandlePrimaryAccount(owner: Uint8Array, programId: Uint8Array): Uint8Array | null;
|
|
50
|
+
/**
|
|
51
|
+
* 32-byte SPL associated token account for `(owner, mint)` under the
|
|
52
|
+
* canonical SPL Token + Associated Token programs (fixed inside the module —
|
|
53
|
+
* they are the same on every SVM chain). Used to check that an address still
|
|
54
|
+
* holds a tokenized handle's NFT.
|
|
55
|
+
*/
|
|
56
|
+
deriveAssociatedTokenAccount(owner: Uint8Array, mint: Uint8Array): Uint8Array | null;
|
|
42
57
|
}
|
|
43
58
|
export {};
|
package/dist/wasm.js
CHANGED
|
@@ -72,4 +72,33 @@ export class WasmResolver {
|
|
|
72
72
|
const n = this.#write(this.#enc.encode(canonical));
|
|
73
73
|
return this.#x.derive_handle_account_staged(n) === 1 ? this.#read() : null;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* 32-byte `@handle` registry `Primary` pointer account for a 32-byte owner
|
|
77
|
+
* address under a given registry program id — seeds `["primary", owner]`.
|
|
78
|
+
* This is the on-chain `set_primary` / `clear_primary` PDA, NOT X1NS's
|
|
79
|
+
* primary-domain record (`derivePrimaryAccount`), which lives under the
|
|
80
|
+
* X1NS registrar and has a different layout.
|
|
81
|
+
*/
|
|
82
|
+
deriveHandlePrimaryAccount(owner, programId) {
|
|
83
|
+
if (owner.length !== 32 || programId.length !== 32)
|
|
84
|
+
return null;
|
|
85
|
+
new Uint8Array(this.#x.memory.buffer, this.#x.program_ptr(), 32).set(programId);
|
|
86
|
+
const n = this.#write(owner);
|
|
87
|
+
return this.#x.derive_handle_primary_account_staged(n) === 1 ? this.#read() : null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 32-byte SPL associated token account for `(owner, mint)` under the
|
|
91
|
+
* canonical SPL Token + Associated Token programs (fixed inside the module —
|
|
92
|
+
* they are the same on every SVM chain). Used to check that an address still
|
|
93
|
+
* holds a tokenized handle's NFT.
|
|
94
|
+
*/
|
|
95
|
+
deriveAssociatedTokenAccount(owner, mint) {
|
|
96
|
+
if (owner.length !== 32 || mint.length !== 32)
|
|
97
|
+
return null;
|
|
98
|
+
const joined = new Uint8Array(64);
|
|
99
|
+
joined.set(owner, 0);
|
|
100
|
+
joined.set(mint, 32);
|
|
101
|
+
const n = this.#write(joined);
|
|
102
|
+
return this.#x.derive_associated_token_account(n) === 1 ? this.#read() : null;
|
|
103
|
+
}
|
|
75
104
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x1id/resolve",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
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
|
-
"exports": {
|
|
9
|
-
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./wasm/x1_resolve_wasm.wasm": "./wasm/x1_resolve_wasm.wasm",
|
|
14
|
+
"./wasm/*": "./wasm/*",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"wasm",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
10
22
|
"scripts": {
|
|
11
23
|
"build:wasm": "cargo build --release -p x1-resolve-wasm --target wasm32-unknown-unknown && cp target/wasm32-unknown-unknown/release/x1_resolve_wasm.wasm wasm/",
|
|
12
24
|
"build": "tsc -p tsconfig.json",
|
|
@@ -14,18 +26,37 @@
|
|
|
14
26
|
"lint:package": "publint",
|
|
15
27
|
"prepublishOnly": "npm run build"
|
|
16
28
|
},
|
|
17
|
-
"keywords": [
|
|
29
|
+
"keywords": [
|
|
30
|
+
"x1",
|
|
31
|
+
"x1id",
|
|
32
|
+
"solana",
|
|
33
|
+
"svm",
|
|
34
|
+
"naming",
|
|
35
|
+
"x1ns",
|
|
36
|
+
"handles",
|
|
37
|
+
"wallet",
|
|
38
|
+
"resolver",
|
|
39
|
+
"web3"
|
|
40
|
+
],
|
|
18
41
|
"homepage": "https://x1id.io",
|
|
19
|
-
"repository": { "type": "git", "url": "git+https://github.com/fortiblox/x1id-sdk.git" },
|
|
20
|
-
"bugs": { "url": "https://github.com/fortiblox/x1id-sdk/issues" },
|
|
21
42
|
"license": "MIT",
|
|
22
|
-
"publishConfig": {
|
|
23
|
-
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
24
49
|
"devDependencies": {
|
|
25
50
|
"typescript": "^5.6.0",
|
|
26
51
|
"@types/node": "^22.0.0",
|
|
27
52
|
"publint": "^0.2.0"
|
|
28
53
|
},
|
|
29
|
-
"peerDependencies": {
|
|
30
|
-
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@solana/web3.js": "^1.95.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"@solana/web3.js": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
31
62
|
}
|
|
Binary file
|