@x1id/resolve 0.11.0 → 0.13.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/README.md CHANGED
@@ -116,6 +116,44 @@ to the other". `@jack.x1` throws `ambiguous` rather than guessing. Use
116
116
  `looksLikeName(input)` to decide whether to attempt resolution at all, so
117
117
  pasting base58 does not surface a validation error.
118
118
 
119
+ ## Scoped names under customer TLDs
120
+
121
+ Anyone can launch their own X1ID TLD (e.g. `.e2eshib`), and names registered
122
+ under it — `hello.e2eshib` — are first-class X1ID names. These are **not** X1NS
123
+ or SNS: `.x1` / `.xnt` / `.xen` are the separate **X1NS** domain system and
124
+ `.sol` is **SNS** (Solana Name Service), each resolved on its own path. A
125
+ customer TLD is resolved natively by this SDK against the `@handle` registry.
126
+
127
+ Scoped resolution derives a per-TLD PDA, which needs `@solana/web3.js`. It is
128
+ **opt-in and explicit** — you wire a deriver in; the SDK never auto-imports
129
+ web3 for you:
130
+
131
+ ```ts
132
+ import { createResolver, WasmResolver, makeScopedHandleDeriver } from "@x1id/resolve";
133
+ import * as web3 from "@solana/web3.js";
134
+
135
+ const wasm = await WasmResolver.fromBytes(/* module bytes */);
136
+
137
+ // `makeScopedHandleDeriver` is ASYNC — await it. Pass the RESULT (not the
138
+ // Promise) as `scopedHandleDeriver`.
139
+ const scopedHandleDeriver = await makeScopedHandleDeriver(web3);
140
+
141
+ const x1id = createResolver({
142
+ rpcUrl: "https://rpc.testnet.x1.xyz",
143
+ wasm,
144
+ scopedHandleDeriver,
145
+ });
146
+
147
+ await x1id.resolve("hello.e2eshib"); // resolves the scoped name under .e2eshib
148
+ ```
149
+
150
+ Without `scopedHandleDeriver`, resolving a launched customer TLD throws
151
+ `ResolveError { code: "not-configured" }` even with `@solana/web3.js` installed —
152
+ the deriver is what the SDK actually calls. And because `makeScopedHandleDeriver`
153
+ returns a Promise, passing it **un-awaited** is a common footgun; the SDK
154
+ detects that and throws a clear `not-configured` error telling you to `await` it,
155
+ rather than a cryptic "scopedHandleKey is not a function".
156
+
119
157
  ## Reads chain state, not an API
120
158
 
121
159
  X1NS names resolve by deriving accounts and reading them over RPC. This library
@@ -274,6 +312,65 @@ for (const a of liveAttestations(atts)) {
274
312
  integrator joining a live attestation to its paired text record reads the same
275
313
  mapping the program, resolver and app badge use.
276
314
 
315
+ ## Attestation gating (allowlists)
316
+
317
+ Gate your app, Discord, mint, or airdrop by whether an `@handle` holds a
318
+ **verified on-chain attestation** — any signal, or a specific platform like
319
+ "verified GitHub." You reuse X1ID's blue-tick oracle as your allowlist instead of
320
+ running your own verification, and the staleness rule (an attestation only counts
321
+ for the *current* owner) is enforced for you.
322
+
323
+ ```ts
324
+ import {
325
+ handlePassesGate, attestationsPassGate, GateKind, type Gate,
326
+ } from "@x1id/resolve";
327
+
328
+ const anyVerified: Gate = "any-verified"; // any blue-tick
329
+ const githubOnly: Gate = { kind: GateKind.github }; // one platform
330
+ const xOrGithub: Gate = { anyOf: [GateKind.x, GateKind.github] };
331
+
332
+ // One-call fetch + check (handleAccount/registeredAt/recordsClearedAt come from
333
+ // the handle you resolved):
334
+ const { passed, attestations } = await handlePassesGate(
335
+ rpc, programId, handleAccount, registeredAt, recordsClearedAt, githubOnly,
336
+ );
337
+ if (!passed) throw new Error("Not allowed — verify your GitHub on X1ID first.");
338
+
339
+ // Already have the attestations? The check is pure + synchronous:
340
+ attestationsPassGate(attestations, xOrGithub); // boolean
341
+ ```
342
+
343
+ `GateKind`: `dns`, `social`, `x`, `discord`, `github`, `telegram`.
344
+
345
+ **On-chain gate (CPI):** another program can verify the same thing inside its own
346
+ transaction via the program-native `assert_attestation_gate` instruction — it
347
+ returns Ok when the gate passes and errors otherwise, so a CPI that errors fails
348
+ the caller's tx. Full call contract (discriminator, accounts, kind codes) is in
349
+ the program's `docs/attestation-gating.md`.
350
+
351
+ **Hosted gate + drop-in middleware (X1ID Gate):** if you'd rather not run your own
352
+ attestation reads, `requireGate()` calls the hosted decision service
353
+ (`POST /v1/gate/check`, api.x1id.io) and returns pass/fail. A tiny, framework-
354
+ agnostic **server** guard (keep your API key server-side):
355
+
356
+ ```ts
357
+ import { requireGate, GateKind } from "@x1id/resolve";
358
+
359
+ const gate = requireGate({ apiKey: process.env.X1ID_API_KEY! }, { kind: GateKind.github });
360
+
361
+ export async function POST(req: Request) { // any server runtime with fetch
362
+ const { handle } = await req.json();
363
+ const { allowed } = await gate({ handle }); // or gate({ wallet })
364
+ if (!allowed) return Response.json({ error: "verified GitHub required" }, { status: 403 });
365
+ // …proceed
366
+ }
367
+ ```
368
+
369
+ Fail-closed by default (re-throws if the decision can't be obtained; pass
370
+ `{ onError: "allow" }` to fail-open). `checkGate()` is the lower-level client for
371
+ the full decision; pass a stored policy's `gp_…` id as the policy to reuse a named
372
+ policy. The hosted check is keyed/metered — see `services/dev-api`'s README.
373
+
277
374
  ## What works today
278
375
 
279
376
  | | Status |
@@ -284,17 +381,19 @@ mapping the program, resolver and app badge use.
284
381
  | Per-chain ETH/BTC records | ✅ read with the staleness rule enforced structurally |
285
382
  | Control proofs (prove you control a `@handle`) | ✅ `createControlChallenge` / `verifyControlProof` — spec: `docs/control-proof.md` |
286
383
  | Records read (per-chain, staleness-enforced) | ✅ `fetchRecords` |
287
- | Subnames · gifts · integrator rev-share · name-lock · record-delegate · text records · attestations (write) | ✅ 0.3.0 — instruction builders; you sign & relay |
288
- | Registration / transfer / `set_primary` (write) | 🔜 roadmap — use [x1id.io](https://x1id.io) |
384
+ | Attestation gating (verified-only allowlists) | ✅ off-chain (`handlePassesGate`) + on-chain CPI (`assert_attestation_gate`) |
385
+ | Registration · lease-to-own · FORTI · commit-reveal · gift-voucher claim (write) | ✅ instruction builders; you sign & relay |
386
+ | Subnames · gifts · integrator rev-share · name-lock · record-delegate · text records · attestations (write) | ✅ instruction builders; you sign & relay |
387
+ | `transfer` / `set_primary` (write) | 🔜 roadmap — use [x1id.io](https://x1id.io) |
289
388
 
290
389
  The `@handle` registry program id is configurable (`handleProgramId`) and
291
390
  defaults to the canonical X1 deployment
292
391
  (`8JgnNWi24bq9uzfnT9XmkWxvaWMVgoEs9bu8QsHhLe1P`).
293
392
 
294
- ## Beyond resolve: the registry write surface (0.3.0)
393
+ ## Beyond resolve: the registry write surface
295
394
 
296
395
  `resolve()` / `reverse()` / `fetchRecords()` are read-only and need only an RPC.
297
- 0.3.0 also ships **instruction builders** for the on-chain registry. Each
396
+ The package also ships **instruction builders** for the on-chain registry. Each
298
397
  returns an unsigned `BuiltInstruction` (`{ programId, keys, data }`) — you
299
398
  assemble it into a transaction, **sign, and relay** yourself; the SDK never
300
399
  holds keys. Every discriminator and account order matches the deployed program.
@@ -328,7 +427,10 @@ const ix = buildCreateSubnameIx({
328
427
  ```
329
428
 
330
429
  **Register a handle** (pays whatever the on-chain ramp charges as of the
331
- landing slot — there is no client-supplied price):
430
+ landing slot — there is no client-supplied price). `buildRegisterIx` builds the
431
+ plain **name-only** `register`; the default **NFT-included** purchase
432
+ (`register_tokenized`, which mints the capability pNFT in the same transaction)
433
+ is driven by app.x1id.io today and has no standalone SDK builder yet:
332
434
 
333
435
  ```ts
334
436
  import { buildRegisterIx, fetchConfigTreasury, HandleType } from "@x1id/resolve";
@@ -369,6 +471,11 @@ Also shipped, same build → sign → relay shape:
369
471
  | Record-write delegation | `buildSetRecordDelegateIx`, `buildRevokeRecordDelegateIx` |
370
472
  | Typed text records (website / avatar / socials) | `buildCreateTextRecordIx`, `buildUpdateTextRecordIx`, `buildCloseTextRecordIx` |
371
473
  | Register a handle | `buildRegisterIx` (+ `fetchConfigTreasury`) |
474
+ | Lease-to-own | `buildRegisterLeasedIx`, `buildRenewLeaseIx`, `buildReclaimExpiredLeaseIx` |
475
+ | Register at a FORTI discount | `buildRegisterWithFortiIx` |
476
+ | Commit-reveal registration (front-run-safe) | `buildCommitIx`, `buildRegisterRevealedIx`, `buildCancelCommitmentIx` |
477
+ | Claim a gifted name (prepaid voucher) | `buildClaimVoucherIx`, `buildClaimVoucherTokenizedIx` |
478
+ | Attestation gating | `handlePassesGate`, `attestationsPassGate`, `GateKind` — see [Attestation gating](#attestation-gating-allowlists) |
372
479
  | Verify a non-SVM address record | `buildCreateRecordIx`, `buildVerifyRecordEthIx`, `buildVerifyRecordSvmIx`, `buildVerifyRecordBtcIx` (+ `buildRecordChallenge`, `splitEthSignature`, `splitBtcSignature`) — see [Verifying a non-SVM address record](#verifying-a-non-svm-address-record) |
373
480
  | Domain / social / per-platform attestations | `buildCreateAttestationIx` (kinds 0–5), `buildCloseAttestationIx` (+ `fetchAttestations`, `isHandleVerified`, `platformSlug`, `textRecordKeyFor`) — see [Per-platform verification](#per-platform-verification-verified-by-platform) |
374
481
  | Agent records (identity, manifest, x402 hints) | `validateManifest`, `agentVerificationLevel`, `x402AcceptsFromManifest`, `agentTextRecords` — see [Agent records](#agent-records) below |
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Admin-only `Config` setters — `set_tiers` / `set_nft_tiers` (#8398) and (as
3
+ * the on-chain admin batch lands) the rest of the AdminOnly config instructions.
4
+ *
5
+ * Hand-rolled like the rest of this package — no Anchor client, no
6
+ * `@solana/web3.js`. All of these share the `AdminOnly` account shape:
7
+ * admin (signer, NOT writable — these setters init nothing, so no rent)
8
+ * config (the ["config"] PDA, writable)
9
+ * and must be signed by `Config.admin`. PDAs are not derived here — derive
10
+ * `["config"]` with your runtime's `findProgramAddress`.
11
+ */
12
+ import type { AddressLike, BuiltInstruction } from "./delegate.js";
13
+ /** Anchor instruction discriminator: `sha256("global:set_tiers")[0..8]`. */
14
+ export declare const SET_TIERS_DISCRIMINATOR: Uint8Array;
15
+ /** Anchor instruction discriminator: `sha256("global:set_nft_tiers")[0..8]`. */
16
+ export declare const SET_NFT_TIERS_DISCRIMINATOR: Uint8Array;
17
+ /** The number of length-tiers in `Config.tier_lamports` / `nft_tier_lamports`.
18
+ * Tier index = `clamp(nameLen, 1, 10) - 1`, so tier[0] = 1-char … tier[9] = 10+. */
19
+ export declare const TIER_COUNT = 10;
20
+ export interface SetTiersParams {
21
+ readonly programId: AddressLike;
22
+ /** `Config.admin` — the ONLY key accepted (has_one = admin). Signs; not mut. */
23
+ readonly admin: AddressLike;
24
+ /** The `["config"]` PDA (writable). */
25
+ readonly config: AddressLike;
26
+ /** Exactly 10 per-length-tier base prices in lamports (tier[0]=1-char …
27
+ * tier[9]=10+). Accepts number or bigint; each must be a non-negative
28
+ * integer that fits u64. */
29
+ readonly tierLamports: ReadonlyArray<number | bigint>;
30
+ }
31
+ /** Build `set_tiers` — replace the 10 length-tier base registration prices. */
32
+ export declare function buildSetTiersIx(p: SetTiersParams): BuiltInstruction;
33
+ export interface SetNftTiersParams {
34
+ readonly programId: AddressLike;
35
+ readonly admin: AddressLike;
36
+ readonly config: AddressLike;
37
+ /** Exactly 10 per-length-tier NFT mint fees in lamports. */
38
+ readonly nftTierLamports: ReadonlyArray<number | bigint>;
39
+ }
40
+ /** Build `set_nft_tiers` — replace the 10 length-tier `mint_handle_nft` fees. */
41
+ export declare function buildSetNftTiersIx(p: SetNftTiersParams): BuiltInstruction;
42
+ export declare const SET_LEASE_RATE_DIVISOR_DISCRIMINATOR: Uint8Array;
43
+ export declare const SET_LEASE_GRACE_PERIOD_DISCRIMINATOR: Uint8Array;
44
+ export declare const SET_RECOVERY_TIMELOCK_DISCRIMINATOR: Uint8Array;
45
+ export declare const SET_MIN_COMMITMENT_AGE_DISCRIMINATOR: Uint8Array;
46
+ export declare const SET_MAX_COMMITMENT_AGE_DISCRIMINATOR: Uint8Array;
47
+ /** Map an admin config action name → its discriminator (for a relay executor
48
+ * that dispatches on the action string). set_admin/set_attestor intentionally
49
+ * absent — those are owner-gated, never dispatched here. */
50
+ export declare const ADMIN_SCALAR_DISCRIMINATORS: Readonly<Record<string, Uint8Array>>;
51
+ export interface AdminScalarParams {
52
+ readonly programId: AddressLike;
53
+ readonly admin: AddressLike;
54
+ readonly config: AddressLike;
55
+ /** The discriminator for the specific setter (from ADMIN_SCALAR_DISCRIMINATORS). */
56
+ readonly discriminator: Uint8Array;
57
+ /** The integer arg (u64/i64 encoding is identical for a non-negative value). */
58
+ readonly value: number | bigint;
59
+ }
60
+ /** Build any one-integer-arg AdminOnly setter (lease rate divisor, lease grace,
61
+ * recovery timelock, min/max commitment age). disc(8) + value as little-endian
62
+ * u64(8). The value must be a non-negative integer that fits u64. */
63
+ export declare function buildAdminScalarSetterIx(p: AdminScalarParams): BuiltInstruction;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Admin-only `Config` setters — `set_tiers` / `set_nft_tiers` (#8398) and (as
3
+ * the on-chain admin batch lands) the rest of the AdminOnly config instructions.
4
+ *
5
+ * Hand-rolled like the rest of this package — no Anchor client, no
6
+ * `@solana/web3.js`. All of these share the `AdminOnly` account shape:
7
+ * admin (signer, NOT writable — these setters init nothing, so no rent)
8
+ * config (the ["config"] PDA, writable)
9
+ * and must be signed by `Config.admin`. PDAs are not derived here — derive
10
+ * `["config"]` with your runtime's `findProgramAddress`.
11
+ */
12
+ import { encodeBase58, decodeBase58_32 } from "./base58.js";
13
+ function toBytes32(v, what) {
14
+ if (typeof v === "string") {
15
+ const b = decodeBase58_32(v);
16
+ if (!b)
17
+ throw new Error(`${what} is not a valid base58 address`);
18
+ return b;
19
+ }
20
+ if (v.length !== 32)
21
+ throw new Error(`${what} must be exactly 32 bytes`);
22
+ return v;
23
+ }
24
+ function toBase58(v, what) {
25
+ return encodeBase58(toBytes32(v, what));
26
+ }
27
+ /** Anchor instruction discriminator: `sha256("global:set_tiers")[0..8]`. */
28
+ export const SET_TIERS_DISCRIMINATOR = Uint8Array.from([
29
+ 170, 131, 109, 175, 156, 214, 177, 121,
30
+ ]);
31
+ /** Anchor instruction discriminator: `sha256("global:set_nft_tiers")[0..8]`. */
32
+ export const SET_NFT_TIERS_DISCRIMINATOR = Uint8Array.from([
33
+ 116, 68, 126, 146, 252, 206, 87, 61,
34
+ ]);
35
+ /** The number of length-tiers in `Config.tier_lamports` / `nft_tier_lamports`.
36
+ * Tier index = `clamp(nameLen, 1, 10) - 1`, so tier[0] = 1-char … tier[9] = 10+. */
37
+ export const TIER_COUNT = 10;
38
+ function encodeTiers(disc, tiers) {
39
+ if (tiers.length !== TIER_COUNT) {
40
+ throw new Error(`expected exactly ${TIER_COUNT} tiers, got ${tiers.length}`);
41
+ }
42
+ // Fixed-size [u64; 10] — borsh writes 10 little-endian u64s back to back, NO
43
+ // length prefix (unlike a Vec or a String).
44
+ const data = new Uint8Array(8 + TIER_COUNT * 8);
45
+ data.set(disc, 0);
46
+ const dv = new DataView(data.buffer);
47
+ for (let i = 0; i < TIER_COUNT; i++) {
48
+ const raw = tiers[i]; // length checked above
49
+ const v = typeof raw === "bigint" ? raw : BigInt(raw);
50
+ if (v < 0n || v > 0xffffffffffffffffn) {
51
+ throw new Error(`tier[${i}] = ${raw} is out of u64 range`);
52
+ }
53
+ dv.setBigUint64(8 + i * 8, v, true);
54
+ }
55
+ return data;
56
+ }
57
+ function adminOnlyKeys(admin, config) {
58
+ return [
59
+ // admin signs but is NOT writable — these setters init nothing (no rent).
60
+ { pubkey: toBase58(admin, "admin"), isSigner: true, isWritable: false },
61
+ { pubkey: toBase58(config, "config"), isSigner: false, isWritable: true },
62
+ ];
63
+ }
64
+ /** Build `set_tiers` — replace the 10 length-tier base registration prices. */
65
+ export function buildSetTiersIx(p) {
66
+ return {
67
+ programId: toBase58(p.programId, "programId"),
68
+ keys: adminOnlyKeys(p.admin, p.config),
69
+ data: encodeTiers(SET_TIERS_DISCRIMINATOR, p.tierLamports),
70
+ };
71
+ }
72
+ /** Build `set_nft_tiers` — replace the 10 length-tier `mint_handle_nft` fees. */
73
+ export function buildSetNftTiersIx(p) {
74
+ return {
75
+ programId: toBase58(p.programId, "programId"),
76
+ keys: adminOnlyKeys(p.admin, p.config),
77
+ data: encodeTiers(SET_NFT_TIERS_DISCRIMINATOR, p.nftTierLamports),
78
+ };
79
+ }
80
+ // ===== Scalar AdminOnly setters (#8399/recovery/commitment) =====
81
+ // Each takes one little-endian integer arg. Same AdminOnly account shape.
82
+ export const SET_LEASE_RATE_DIVISOR_DISCRIMINATOR = Uint8Array.from([181, 218, 76, 213, 80, 99, 181, 52]);
83
+ export const SET_LEASE_GRACE_PERIOD_DISCRIMINATOR = Uint8Array.from([212, 2, 110, 20, 130, 50, 194, 186]);
84
+ export const SET_RECOVERY_TIMELOCK_DISCRIMINATOR = Uint8Array.from([178, 183, 148, 243, 155, 166, 79, 6]);
85
+ export const SET_MIN_COMMITMENT_AGE_DISCRIMINATOR = Uint8Array.from([181, 20, 114, 43, 51, 23, 47, 248]);
86
+ export const SET_MAX_COMMITMENT_AGE_DISCRIMINATOR = Uint8Array.from([163, 106, 196, 206, 146, 156, 79, 249]);
87
+ /** Map an admin config action name → its discriminator (for a relay executor
88
+ * that dispatches on the action string). set_admin/set_attestor intentionally
89
+ * absent — those are owner-gated, never dispatched here. */
90
+ export const ADMIN_SCALAR_DISCRIMINATORS = {
91
+ set_lease_rate_divisor: SET_LEASE_RATE_DIVISOR_DISCRIMINATOR,
92
+ set_lease_grace_period: SET_LEASE_GRACE_PERIOD_DISCRIMINATOR,
93
+ set_recovery_timelock: SET_RECOVERY_TIMELOCK_DISCRIMINATOR,
94
+ set_min_commitment_age: SET_MIN_COMMITMENT_AGE_DISCRIMINATOR,
95
+ set_max_commitment_age: SET_MAX_COMMITMENT_AGE_DISCRIMINATOR,
96
+ };
97
+ /** Build any one-integer-arg AdminOnly setter (lease rate divisor, lease grace,
98
+ * recovery timelock, min/max commitment age). disc(8) + value as little-endian
99
+ * u64(8). The value must be a non-negative integer that fits u64. */
100
+ export function buildAdminScalarSetterIx(p) {
101
+ const raw = typeof p.value === "bigint" ? p.value : BigInt(p.value);
102
+ if (raw < 0n || raw > 0xffffffffffffffffn) {
103
+ throw new Error(`value ${p.value} is out of u64 range`);
104
+ }
105
+ const data = new Uint8Array(8 + 8);
106
+ data.set(p.discriminator, 0);
107
+ new DataView(data.buffer).setBigUint64(8, raw, true);
108
+ return {
109
+ programId: toBase58(p.programId, "programId"),
110
+ keys: adminOnlyKeys(p.admin, p.config),
111
+ data,
112
+ };
113
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Admin-only marketplace config (`set_market_config`, #8401) and the namespace
3
+ * directory setters (`create_namespace` / `set_namespace_status`, #8403). The
4
+ * integrator setters (add/set_rate/remove) already live in ./integrator.ts and
5
+ * are reused, not duplicated here.
6
+ *
7
+ * Hand-rolled like the rest of this package — no Anchor client, no
8
+ * `@solana/web3.js`. Unlike the AdminOnly setters in adminConfig.ts, these do
9
+ * NOT share one account shape: `config` is read-only here (it's the has_one =
10
+ * admin authority check), and the written account differs per instruction
11
+ * (`market_config` or a `Namespace`). PDAs are not derived here — derive
12
+ * `["market_config"]`, `["namespace", label]`, `["config"]` with your runtime's
13
+ * findProgramAddress and pass them in, same convention as adminConfig.ts.
14
+ */
15
+ import type { AddressLike, BuiltInstruction } from "./delegate.js";
16
+ /** The System Program — required by the two `init` instructions (add_integrator,
17
+ * create_namespace). A fixed well-known address, hardcoded so callers can't
18
+ * mistype it. */
19
+ export declare const SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
20
+ /** Anchor discriminator sha256("global:set_market_config")[0..8]. */
21
+ export declare const SET_MARKET_CONFIG_DISCRIMINATOR: Uint8Array;
22
+ export interface SetMarketConfigParams {
23
+ readonly programId: AddressLike;
24
+ /** `Config.admin` — signs; NOT writable. */
25
+ readonly admin: AddressLike;
26
+ /** `["config"]` PDA — read-only (the has_one authority check). */
27
+ readonly config: AddressLike;
28
+ /** `["market_config"]` PDA — writable. */
29
+ readonly marketConfig: AddressLike;
30
+ readonly feeBps: number;
31
+ readonly minBidIncrementBps: number;
32
+ readonly antiSnipeWindow: number;
33
+ readonly antiSnipeExtend: number;
34
+ readonly minAuctionSecs: number;
35
+ readonly maxAuctionSecs: number;
36
+ readonly paused: boolean;
37
+ }
38
+ /** Build `set_market_config` — fee/auction params + the global pause flag.
39
+ * Enforces the same bounds the program's validate_market_params does. */
40
+ export declare function buildSetMarketConfigIx(p: SetMarketConfigParams): BuiltInstruction;
41
+ export declare const CREATE_NAMESPACE_DISCRIMINATOR: Uint8Array;
42
+ export declare const SET_NAMESPACE_STATUS_DISCRIMINATOR: Uint8Array;
43
+ export declare const NAMESPACE_MAX_LEN = 16;
44
+ export type NamespaceStatus = "Active" | "Paused" | "Retired";
45
+ export interface CreateNamespaceParams {
46
+ readonly programId: AddressLike;
47
+ /** admin — signs AND pays rent; writable. */
48
+ readonly admin: AddressLike;
49
+ readonly config: AddressLike;
50
+ /** `["namespace", label]` PDA — init, writable. */
51
+ readonly namespace: AddressLike;
52
+ /** Canonical ASCII label (a-z 0-9 -), 1..=16 bytes — an instruction ARG. */
53
+ readonly label: string;
54
+ readonly systemProgram?: AddressLike;
55
+ }
56
+ /** Build `create_namespace` — label is a borsh String arg. */
57
+ export declare function buildCreateNamespaceIx(p: CreateNamespaceParams): BuiltInstruction;
58
+ export interface SetNamespaceStatusParams {
59
+ readonly programId: AddressLike;
60
+ readonly admin: AddressLike;
61
+ readonly config: AddressLike;
62
+ /** `["namespace", label]` PDA — writable. */
63
+ readonly namespace: AddressLike;
64
+ readonly status: NamespaceStatus;
65
+ }
66
+ /** Build `set_namespace_status` — status enum as a single-byte discriminant. */
67
+ export declare function buildSetNamespaceStatusIx(p: SetNamespaceStatusParams): BuiltInstruction;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Admin-only marketplace config (`set_market_config`, #8401) and the namespace
3
+ * directory setters (`create_namespace` / `set_namespace_status`, #8403). The
4
+ * integrator setters (add/set_rate/remove) already live in ./integrator.ts and
5
+ * are reused, not duplicated here.
6
+ *
7
+ * Hand-rolled like the rest of this package — no Anchor client, no
8
+ * `@solana/web3.js`. Unlike the AdminOnly setters in adminConfig.ts, these do
9
+ * NOT share one account shape: `config` is read-only here (it's the has_one =
10
+ * admin authority check), and the written account differs per instruction
11
+ * (`market_config` or a `Namespace`). PDAs are not derived here — derive
12
+ * `["market_config"]`, `["namespace", label]`, `["config"]` with your runtime's
13
+ * findProgramAddress and pass them in, same convention as adminConfig.ts.
14
+ */
15
+ import { encodeBase58, decodeBase58_32 } from "./base58.js";
16
+ /** The System Program — required by the two `init` instructions (add_integrator,
17
+ * create_namespace). A fixed well-known address, hardcoded so callers can't
18
+ * mistype it. */
19
+ export const SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
20
+ function toBytes32(v, what) {
21
+ if (typeof v === "string") {
22
+ const b = decodeBase58_32(v);
23
+ if (!b)
24
+ throw new Error(`${what} is not a valid base58 address`);
25
+ return b;
26
+ }
27
+ if (v.length !== 32)
28
+ throw new Error(`${what} must be exactly 32 bytes`);
29
+ return v;
30
+ }
31
+ function toBase58(v, what) {
32
+ return encodeBase58(toBytes32(v, what));
33
+ }
34
+ // ---- borsh scalar writers -------------------------------------------------
35
+ function u16le(n) {
36
+ if (!Number.isInteger(n) || n < 0 || n > 0xffff)
37
+ throw new Error(`u16 out of range: ${n}`);
38
+ const b = new Uint8Array(2);
39
+ new DataView(b.buffer).setUint16(0, n, true);
40
+ return b;
41
+ }
42
+ function u32le(n) {
43
+ if (!Number.isInteger(n) || n < 0 || n > 0xffff_ffff)
44
+ throw new Error(`u32 out of range: ${n}`);
45
+ const b = new Uint8Array(4);
46
+ new DataView(b.buffer).setUint32(0, n, true);
47
+ return b;
48
+ }
49
+ function concat(parts) {
50
+ const len = parts.reduce((n, p) => n + p.length, 0);
51
+ const out = new Uint8Array(len);
52
+ let o = 0;
53
+ for (const p of parts) {
54
+ out.set(p, o);
55
+ o += p.length;
56
+ }
57
+ return out;
58
+ }
59
+ // ===== #8401 set_market_config =====
60
+ /** Anchor discriminator sha256("global:set_market_config")[0..8]. */
61
+ export const SET_MARKET_CONFIG_DISCRIMINATOR = Uint8Array.from([128, 237, 216, 59, 122, 62, 156, 30]);
62
+ /** Build `set_market_config` — fee/auction params + the global pause flag.
63
+ * Enforces the same bounds the program's validate_market_params does. */
64
+ export function buildSetMarketConfigIx(p) {
65
+ if (p.feeBps > 10_000)
66
+ throw new Error("feeBps must be <= 10000");
67
+ if (p.minBidIncrementBps > 10_000)
68
+ throw new Error("minBidIncrementBps must be <= 10000");
69
+ if (p.minAuctionSecs <= 0)
70
+ throw new Error("minAuctionSecs must be > 0");
71
+ if (p.maxAuctionSecs < p.minAuctionSecs)
72
+ throw new Error("maxAuctionSecs must be >= minAuctionSecs");
73
+ if (p.antiSnipeExtend < p.antiSnipeWindow)
74
+ throw new Error("antiSnipeExtend must be >= antiSnipeWindow");
75
+ const data = concat([
76
+ SET_MARKET_CONFIG_DISCRIMINATOR,
77
+ u16le(p.feeBps),
78
+ u16le(p.minBidIncrementBps),
79
+ u32le(p.antiSnipeWindow),
80
+ u32le(p.antiSnipeExtend),
81
+ u32le(p.minAuctionSecs),
82
+ u32le(p.maxAuctionSecs),
83
+ Uint8Array.from([p.paused ? 1 : 0]),
84
+ ]);
85
+ const keys = [
86
+ { pubkey: toBase58(p.admin, "admin"), isSigner: true, isWritable: false },
87
+ { pubkey: toBase58(p.config, "config"), isSigner: false, isWritable: false },
88
+ { pubkey: toBase58(p.marketConfig, "marketConfig"), isSigner: false, isWritable: true },
89
+ ];
90
+ return { programId: toBase58(p.programId, "programId"), keys, data };
91
+ }
92
+ // NOTE: the integrator builders (add_integrator / set_integrator_rate /
93
+ // remove_integrator) + decodeAllowlistEntry already live in ./integrator.ts —
94
+ // reuse those, they are not duplicated here.
95
+ // ===== #8403 namespaces =====
96
+ export const CREATE_NAMESPACE_DISCRIMINATOR = Uint8Array.from([205, 189, 35, 255, 214, 116, 25, 107]);
97
+ export const SET_NAMESPACE_STATUS_DISCRIMINATOR = Uint8Array.from([41, 135, 103, 92, 151, 13, 61, 158]);
98
+ export const NAMESPACE_MAX_LEN = 16;
99
+ const NAMESPACE_STATUS_DISCRIMINANT = {
100
+ Active: 0,
101
+ Paused: 1,
102
+ Retired: 2,
103
+ };
104
+ function borshString(s) {
105
+ const bytes = new TextEncoder().encode(s);
106
+ return concat([u32le(bytes.length), bytes]);
107
+ }
108
+ /** Build `create_namespace` — label is a borsh String arg. */
109
+ export function buildCreateNamespaceIx(p) {
110
+ if (p.label.length < 1 || p.label.length > NAMESPACE_MAX_LEN) {
111
+ throw new Error(`label must be 1..=${NAMESPACE_MAX_LEN} chars`);
112
+ }
113
+ const data = concat([CREATE_NAMESPACE_DISCRIMINATOR, borshString(p.label)]);
114
+ const keys = [
115
+ { pubkey: toBase58(p.admin, "admin"), isSigner: true, isWritable: true },
116
+ { pubkey: toBase58(p.config, "config"), isSigner: false, isWritable: false },
117
+ { pubkey: toBase58(p.namespace, "namespace"), isSigner: false, isWritable: true },
118
+ { pubkey: toBase58(p.systemProgram ?? SYSTEM_PROGRAM_ID, "systemProgram"), isSigner: false, isWritable: false },
119
+ ];
120
+ return { programId: toBase58(p.programId, "programId"), keys, data };
121
+ }
122
+ /** Build `set_namespace_status` — status enum as a single-byte discriminant. */
123
+ export function buildSetNamespaceStatusIx(p) {
124
+ const disc = NAMESPACE_STATUS_DISCRIMINANT[p.status];
125
+ if (disc === undefined)
126
+ throw new Error(`unknown namespace status: ${p.status}`);
127
+ const data = concat([SET_NAMESPACE_STATUS_DISCRIMINATOR, Uint8Array.from([disc])]);
128
+ const keys = [
129
+ { pubkey: toBase58(p.admin, "admin"), isSigner: true, isWritable: false },
130
+ { pubkey: toBase58(p.config, "config"), isSigner: false, isWritable: false },
131
+ { pubkey: toBase58(p.namespace, "namespace"), isSigner: false, isWritable: true },
132
+ ];
133
+ return { programId: toBase58(p.programId, "programId"), keys, data };
134
+ }