@x1id/resolve 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -52,6 +52,7 @@ export * from "./recordWrite.js";
52
52
  export * from "./clearRecords.js";
53
53
  export * from "./commitReveal.js";
54
54
  export * from "./pnftTransfer.js";
55
+ export * from "./mintNft.js";
55
56
  export * from "./payment.js";
56
57
  export * from "./signin.js";
57
58
  export * from "./connect.js";
package/dist/index.js CHANGED
@@ -59,6 +59,7 @@ export * from "./recordWrite.js";
59
59
  export * from "./clearRecords.js";
60
60
  export * from "./commitReveal.js";
61
61
  export * from "./pnftTransfer.js";
62
+ export * from "./mintNft.js";
62
63
  export * from "./payment.js";
63
64
  export * from "./signin.js";
64
65
  export * from "./connect.js";
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Tokenized registration — the capability-pNFT instructions of the registry
3
+ * program: `mint_handle_nft` (tokenize an already-registered handle),
4
+ * `mint_handle_nft_with_uri` (same, caller-pinned metadata URI),
5
+ * `register_tokenized` (register + mint in ONE instruction — the default
6
+ * permanent-XNT purchase) and `register_revealed_tokenized` (its
7
+ * commit-reveal sibling).
8
+ *
9
+ * The minted NFT is a Metaplex ProgrammableNonFungible (pNFT) VERIFIED into
10
+ * the "X1ID Handles" collection, with treasury royalties. Its token account
11
+ * is FROZEN by Token Metadata; move it with `buildTransferV1Ix`
12
+ * (pnftTransfer.ts), never a plain SPL transfer.
13
+ *
14
+ * One backend call for a tokenized purchase (payer = owner = the user):
15
+ *
16
+ * ```ts
17
+ * import { PublicKey } from "@solana/web3.js";
18
+ * import {
19
+ * deriveTokenizedNftAccounts, buildRegisterTokenizedIx, fetchConfigTreasury,
20
+ * REGISTER_TOKENIZED_CU_LIMIT, HandleType,
21
+ * } from "@x1id/resolve";
22
+ *
23
+ * const find = (seeds, pid) => PublicKey.findProgramAddressSync(seeds, new PublicKey(pid))[0].toBase58();
24
+ * const te = new TextEncoder();
25
+ * const handle = find([te.encode("handle"), te.encode(canonical)], programId);
26
+ * const accounts = await deriveTokenizedNftAccounts(find, { programId, owner: user, handle });
27
+ * const treasury = await fetchConfigTreasury(rpc, accounts.config);
28
+ * const ix = buildRegisterTokenizedIx({
29
+ * ...accounts, programId, payer: user, owner: user, handle, treasury,
30
+ * name: canonical, handleType: HandleType.Human,
31
+ * });
32
+ * // prepend ComputeBudget SetComputeUnitLimit(REGISTER_TOKENIZED_CU_LIMIT)
33
+ * ```
34
+ *
35
+ * # Account order (source of truth: programs/x1-handles/src/lib.rs)
36
+ *
37
+ * - `MintHandleNft<'info>`: payer(s,w) owner(s) config(w) treasury(w)
38
+ * handle(w) auction listing mint(w) owner_token_account(w) metadata(w)
39
+ * master_edition(w) token_record(w) collection_mint collection_metadata(w)
40
+ * collection_master_edition collection_authority token_metadata_program
41
+ * token_program associated_token_program system_program
42
+ * sysvar_instructions.
43
+ * - `RegisterTokenized<'info>`: payer(s,w) owner config(w) treasury(w)
44
+ * handle(w), then the SAME `mint` … `sysvar_instructions` tail (no
45
+ * auction/listing — a just-created handle can have neither), then the
46
+ * optional integrator(w) / integrator_allowlist pair.
47
+ * - `RegisterRevealedTokenized<'info>`: as `RegisterTokenized` plus
48
+ * commitment(w) right after handle.
49
+ *
50
+ * Note `owner` SIGNS `mint_handle_nft` (it tokenizes its own handle) but does
51
+ * NOT sign the register variants (gifting parity with `register`).
52
+ *
53
+ * # Compute budget
54
+ *
55
+ * The pNFT path (CreateV1 + MintV1 + VerifyCollectionV1 in one program
56
+ * instruction) exceeds the 200k-CU per-instruction default: every
57
+ * transaction carrying one of these instructions MUST also carry a
58
+ * ComputeBudget `SetComputeUnitLimit` — {@link MINT_HANDLE_NFT_CU_LIMIT} or
59
+ * {@link REGISTER_TOKENIZED_CU_LIMIT}. X1 prices the fee by the REQUESTED
60
+ * limit, so these are sized close to measured mainnet consumption.
61
+ *
62
+ * Hand-rolled like the rest of this package: no Anchor client, no
63
+ * `@solana/web3.js` import, no `@types/node`. Builders are pure (no RPC);
64
+ * PDAs are derived through an INJECTED `find_program_address`
65
+ * ({@link PdaFinder}) because this package never hand-rolls the ed25519
66
+ * on-curve check.
67
+ */
68
+ import type { AddressLike, BuiltInstruction, InstructionKey } from "./delegate.js";
69
+ import type { PdaFinder } from "./namespacePricing.js";
70
+ import type { HandleTypeValue } from "./voucher.js";
71
+ /** `sha256("global:mint_handle_nft")[0..8]`. */
72
+ export declare const MINT_HANDLE_NFT_DISCRIMINATOR: Uint8Array;
73
+ /** `sha256("global:mint_handle_nft_with_uri")[0..8]`. */
74
+ export declare const MINT_HANDLE_NFT_WITH_URI_DISCRIMINATOR: Uint8Array;
75
+ /** `sha256("global:register_tokenized")[0..8]`. */
76
+ export declare const REGISTER_TOKENIZED_DISCRIMINATOR: Uint8Array;
77
+ /** `sha256("global:register_revealed_tokenized")[0..8]`. */
78
+ export declare const REGISTER_REVEALED_TOKENIZED_DISCRIMINATOR: Uint8Array;
79
+ /** Compute-unit limit for any transaction carrying `mint_handle_nft` /
80
+ * `mint_handle_nft_with_uri` (measured ~318k CU on mainnet; ~41% headroom). */
81
+ export declare const MINT_HANDLE_NFT_CU_LIMIT = 450000;
82
+ /** Compute-unit limit for any transaction carrying `register_tokenized` /
83
+ * `register_revealed_tokenized` (measured 299k–347k CU live; ≥17% headroom). */
84
+ export declare const REGISTER_TOKENIZED_CU_LIMIT = 420000;
85
+ /** `mpl_token_metadata::MAX_URI_LENGTH` — `mint_handle_nft_with_uri` refuses
86
+ * (`UriTooLong`) a longer URI; checked client-side too. */
87
+ export declare const MAX_NFT_METADATA_URI_LENGTH = 200;
88
+ /** Seed prefix of a handle's NFT mint PDA: `["nft_mint", handle]`. */
89
+ export declare const NFT_MINT_SEED = "nft_mint";
90
+ /** Seed of the collection parent's mint PDA: `["collection_mint"]`. */
91
+ export declare const COLLECTION_MINT_SEED = "collection_mint";
92
+ /** Seed of the collection update-authority PDA: `["collection_authority"]`. */
93
+ export declare const COLLECTION_AUTHORITY_SEED = "collection_authority";
94
+ /** The pNFT account block every tokenized instruction carries from `mint`
95
+ * onward, plus the `["config"]` PDA. Everything here is a PDA/ATA derived
96
+ * from `(programId, owner, handle)` — see {@link deriveTokenizedNftAccounts}. */
97
+ export interface TokenizedNftAccounts {
98
+ /** The `["config"]` PDA. */
99
+ readonly config: string;
100
+ /** The handle's NFT mint — `["nft_mint", handle]` under the registry program. */
101
+ readonly mint: string;
102
+ /** `owner`'s ATA for `mint` (classic SPL Token) — receives the single unit, frozen. */
103
+ readonly ownerTokenAccount: string;
104
+ /** Metaplex metadata PDA for `mint`. */
105
+ readonly metadata: string;
106
+ /** Metaplex master-edition PDA for `mint`. */
107
+ readonly masterEdition: string;
108
+ /** pNFT `TokenRecord` PDA for (`mint`, `ownerTokenAccount`). */
109
+ readonly tokenRecord: string;
110
+ /** Collection parent's mint — `["collection_mint"]` under the registry program. */
111
+ readonly collectionMint: string;
112
+ /** Collection parent's metadata (writable — sized-collection count bump). */
113
+ readonly collectionMetadata: string;
114
+ /** Collection parent's master edition. */
115
+ readonly collectionMasterEdition: string;
116
+ /** `["collection_authority"]` program-signer PDA. */
117
+ readonly collectionAuthority: string;
118
+ }
119
+ /** {@link TokenizedNftAccounts} plus the two read-only market PDAs
120
+ * `mint_handle_nft` checks (no live auction/listing may exist). */
121
+ export interface MintHandleNftAccounts extends TokenizedNftAccounts {
122
+ /** `["auction", handle]` under the registry program (normally absent). */
123
+ readonly auction: string;
124
+ /** `["listing", handle]` under the registry program (normally absent). */
125
+ readonly listing: string;
126
+ }
127
+ export interface TokenizedNftAccountsInput {
128
+ /** The registry program id. */
129
+ readonly programId: AddressLike;
130
+ /** The wallet that will hold the NFT (its ATA + token record are derived). */
131
+ readonly owner: AddressLike;
132
+ /** The `["handle", name]` PDA being tokenized. */
133
+ readonly handle: AddressLike;
134
+ }
135
+ /**
136
+ * Derive every PDA/ATA a tokenized REGISTER (`register_tokenized` /
137
+ * `register_revealed_tokenized`) needs beyond payer/owner/handle/treasury.
138
+ * Pure: nothing is fetched. `Config.treasury` is NOT derivable — read it with
139
+ * `fetchConfigTreasury` (register.ts).
140
+ */
141
+ export declare function deriveTokenizedNftAccounts(find: PdaFinder, p: TokenizedNftAccountsInput): Promise<TokenizedNftAccounts>;
142
+ /**
143
+ * Derive every PDA/ATA `mint_handle_nft` needs beyond payer/owner/handle/
144
+ * treasury: {@link deriveTokenizedNftAccounts} plus `["auction", handle]` and
145
+ * `["listing", handle]`. Pure: nothing is fetched.
146
+ */
147
+ export declare function deriveMintHandleNftAccounts(find: PdaFinder, p: TokenizedNftAccountsInput): Promise<MintHandleNftAccounts>;
148
+ /** The `mint` … `collectionAuthority` addresses — any object carrying them
149
+ * (e.g. a {@link TokenizedNftAccounts}, or a per-TLD collection variant). */
150
+ export interface TokenizedTailAccounts {
151
+ readonly mint: AddressLike;
152
+ readonly ownerTokenAccount: AddressLike;
153
+ readonly metadata: AddressLike;
154
+ readonly masterEdition: AddressLike;
155
+ readonly tokenRecord: AddressLike;
156
+ readonly collectionMint: AddressLike;
157
+ readonly collectionMetadata: AddressLike;
158
+ readonly collectionMasterEdition: AddressLike;
159
+ readonly collectionAuthority: AddressLike;
160
+ }
161
+ /**
162
+ * The 14-account pNFT tail shared by every tokenized instruction, from
163
+ * `mint` through `sysvar_instructions` (order + signer/writable flags exactly
164
+ * as `MintHandleNft` / `RegisterTokenized` declare them in lib.rs). Exposed
165
+ * for instructions that append the same tail to their own preamble.
166
+ */
167
+ export declare function buildTokenizedTailKeys(a: TokenizedTailAccounts): InstructionKey[];
168
+ export interface MintHandleNftParams extends TokenizedTailAccounts {
169
+ /** The registry program id. */
170
+ readonly programId: AddressLike;
171
+ /** Pays the mint fee + all new-account rent. Signer. */
172
+ readonly payer: AddressLike;
173
+ /** The handle's current owner — SIGNS (normally the same wallet as payer). */
174
+ readonly owner: AddressLike;
175
+ /** The `["config"]` PDA. */
176
+ readonly config: AddressLike;
177
+ /** `Config.treasury` — `fetchConfigTreasury` (register.ts). */
178
+ readonly treasury: AddressLike;
179
+ /** The existing, untokenized `["handle", name]` PDA. */
180
+ readonly handle: AddressLike;
181
+ /** `["auction", handle]` — read-only. */
182
+ readonly auction: AddressLike;
183
+ /** `["listing", handle]` — read-only. */
184
+ readonly listing: AddressLike;
185
+ }
186
+ /**
187
+ * Build `mint_handle_nft` — tokenize an ALREADY-REGISTERED, untokenized
188
+ * handle as its capability pNFT. Signers: `payer` AND `owner` (two signature
189
+ * slots unless they are the same key). Account order: `MintHandleNft<'info>`
190
+ * (see module docs). Data is the 8-byte discriminator only. Prepend
191
+ * `SetComputeUnitLimit(MINT_HANDLE_NFT_CU_LIMIT)`.
192
+ */
193
+ export declare function buildMintHandleNftIx(p: MintHandleNftParams): BuiltInstruction;
194
+ export interface MintHandleNftWithUriParams extends MintHandleNftParams {
195
+ /** A metadata URI the caller has ALREADY pinned (Arweave/IPFS), ≤ 200 bytes. */
196
+ readonly metadataUri: string;
197
+ }
198
+ /**
199
+ * Build `mint_handle_nft_with_uri` — {@link buildMintHandleNftIx} with a
200
+ * caller-supplied Metaplex `uri` instead of the program's hosted one. Same
201
+ * accounts and signers; data = discriminator ‖ borsh(String metadataUri).
202
+ */
203
+ export declare function buildMintHandleNftWithUriIx(p: MintHandleNftWithUriParams): BuiltInstruction;
204
+ export interface RegisterTokenizedParams extends TokenizedTailAccounts {
205
+ /** The registry program id. */
206
+ readonly programId: AddressLike;
207
+ /** Pays the price and all rent. Signer. */
208
+ readonly payer: AddressLike;
209
+ /** Receives the handle + NFT. Need NOT sign (gifting parity with `register`). */
210
+ readonly owner: AddressLike;
211
+ /** The `["config"]` PDA. */
212
+ readonly config: AddressLike;
213
+ /** `Config.treasury` — `fetchConfigTreasury` (register.ts). */
214
+ readonly treasury: AddressLike;
215
+ /** The `["handle", name]` PDA being claimed — must not already exist. */
216
+ readonly handle: AddressLike;
217
+ /** Canonical form (`normalizeHandle` output), 1..=32 bytes. */
218
+ readonly name: string;
219
+ readonly handleType: HandleTypeValue;
220
+ /** OPTIONAL revenue-share pair (#7397) — supply BOTH or NEITHER. */
221
+ readonly integrator?: AddressLike;
222
+ /** The `["integrator", integrator]` allowlist PDA. */
223
+ readonly integratorAllowlist?: AddressLike;
224
+ }
225
+ /**
226
+ * Build `register_tokenized` — register a new handle AND mint its pNFT in
227
+ * one instruction (the default permanent-XNT purchase). Signer: `payer`
228
+ * only. Account order: `RegisterTokenized<'info>` (see module docs). Data =
229
+ * discriminator ‖ borsh(name) ‖ handle_type(u8). Price is computed on-chain
230
+ * (as for `register`). Prepend `SetComputeUnitLimit(REGISTER_TOKENIZED_CU_LIMIT)`.
231
+ * `name` must be canonical (the PDA seeds on it).
232
+ */
233
+ export declare function buildRegisterTokenizedIx(p: RegisterTokenizedParams): BuiltInstruction;
234
+ export interface RegisterRevealedTokenizedParams extends RegisterTokenizedParams {
235
+ /** The `["commitment", commitmentHash]` PDA the matching `commit` created. */
236
+ readonly commitment: AddressLike;
237
+ /** The exact 32-byte secret the commitment hashed. */
238
+ readonly secret: Uint8Array;
239
+ }
240
+ /**
241
+ * Build `register_revealed_tokenized` — the commit-reveal sibling of
242
+ * {@link buildRegisterTokenizedIx}. Signer: `payer` only. Same accounts plus
243
+ * `commitment` (w) right after `handle`; data = discriminator ‖ borsh(name)
244
+ * ‖ handle_type(u8) ‖ secret[32]. Prepend
245
+ * `SetComputeUnitLimit(REGISTER_TOKENIZED_CU_LIMIT)`.
246
+ */
247
+ export declare function buildRegisterRevealedTokenizedIx(p: RegisterRevealedTokenizedParams): BuiltInstruction;
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Tokenized registration — the capability-pNFT instructions of the registry
3
+ * program: `mint_handle_nft` (tokenize an already-registered handle),
4
+ * `mint_handle_nft_with_uri` (same, caller-pinned metadata URI),
5
+ * `register_tokenized` (register + mint in ONE instruction — the default
6
+ * permanent-XNT purchase) and `register_revealed_tokenized` (its
7
+ * commit-reveal sibling).
8
+ *
9
+ * The minted NFT is a Metaplex ProgrammableNonFungible (pNFT) VERIFIED into
10
+ * the "X1ID Handles" collection, with treasury royalties. Its token account
11
+ * is FROZEN by Token Metadata; move it with `buildTransferV1Ix`
12
+ * (pnftTransfer.ts), never a plain SPL transfer.
13
+ *
14
+ * One backend call for a tokenized purchase (payer = owner = the user):
15
+ *
16
+ * ```ts
17
+ * import { PublicKey } from "@solana/web3.js";
18
+ * import {
19
+ * deriveTokenizedNftAccounts, buildRegisterTokenizedIx, fetchConfigTreasury,
20
+ * REGISTER_TOKENIZED_CU_LIMIT, HandleType,
21
+ * } from "@x1id/resolve";
22
+ *
23
+ * const find = (seeds, pid) => PublicKey.findProgramAddressSync(seeds, new PublicKey(pid))[0].toBase58();
24
+ * const te = new TextEncoder();
25
+ * const handle = find([te.encode("handle"), te.encode(canonical)], programId);
26
+ * const accounts = await deriveTokenizedNftAccounts(find, { programId, owner: user, handle });
27
+ * const treasury = await fetchConfigTreasury(rpc, accounts.config);
28
+ * const ix = buildRegisterTokenizedIx({
29
+ * ...accounts, programId, payer: user, owner: user, handle, treasury,
30
+ * name: canonical, handleType: HandleType.Human,
31
+ * });
32
+ * // prepend ComputeBudget SetComputeUnitLimit(REGISTER_TOKENIZED_CU_LIMIT)
33
+ * ```
34
+ *
35
+ * # Account order (source of truth: programs/x1-handles/src/lib.rs)
36
+ *
37
+ * - `MintHandleNft<'info>`: payer(s,w) owner(s) config(w) treasury(w)
38
+ * handle(w) auction listing mint(w) owner_token_account(w) metadata(w)
39
+ * master_edition(w) token_record(w) collection_mint collection_metadata(w)
40
+ * collection_master_edition collection_authority token_metadata_program
41
+ * token_program associated_token_program system_program
42
+ * sysvar_instructions.
43
+ * - `RegisterTokenized<'info>`: payer(s,w) owner config(w) treasury(w)
44
+ * handle(w), then the SAME `mint` … `sysvar_instructions` tail (no
45
+ * auction/listing — a just-created handle can have neither), then the
46
+ * optional integrator(w) / integrator_allowlist pair.
47
+ * - `RegisterRevealedTokenized<'info>`: as `RegisterTokenized` plus
48
+ * commitment(w) right after handle.
49
+ *
50
+ * Note `owner` SIGNS `mint_handle_nft` (it tokenizes its own handle) but does
51
+ * NOT sign the register variants (gifting parity with `register`).
52
+ *
53
+ * # Compute budget
54
+ *
55
+ * The pNFT path (CreateV1 + MintV1 + VerifyCollectionV1 in one program
56
+ * instruction) exceeds the 200k-CU per-instruction default: every
57
+ * transaction carrying one of these instructions MUST also carry a
58
+ * ComputeBudget `SetComputeUnitLimit` — {@link MINT_HANDLE_NFT_CU_LIMIT} or
59
+ * {@link REGISTER_TOKENIZED_CU_LIMIT}. X1 prices the fee by the REQUESTED
60
+ * limit, so these are sized close to measured mainnet consumption.
61
+ *
62
+ * Hand-rolled like the rest of this package: no Anchor client, no
63
+ * `@solana/web3.js` import, no `@types/node`. Builders are pure (no RPC);
64
+ * PDAs are derived through an INJECTED `find_program_address`
65
+ * ({@link PdaFinder}) because this package never hand-rolls the ed25519
66
+ * on-curve check.
67
+ */
68
+ import { encodeBase58, decodeBase58_32 } from "./base58.js";
69
+ import { normalizeHandle } from "./parse.js";
70
+ import { MPL_TOKEN_METADATA_PROGRAM } from "./pnftTransfer.js";
71
+ /** `sha256("global:mint_handle_nft")[0..8]`. */
72
+ export const MINT_HANDLE_NFT_DISCRIMINATOR = Uint8Array.from([
73
+ 237, 69, 220, 121, 210, 168, 2, 193,
74
+ ]);
75
+ /** `sha256("global:mint_handle_nft_with_uri")[0..8]`. */
76
+ export const MINT_HANDLE_NFT_WITH_URI_DISCRIMINATOR = Uint8Array.from([
77
+ 19, 122, 117, 92, 55, 158, 255, 208,
78
+ ]);
79
+ /** `sha256("global:register_tokenized")[0..8]`. */
80
+ export const REGISTER_TOKENIZED_DISCRIMINATOR = Uint8Array.from([
81
+ 226, 155, 236, 28, 221, 160, 186, 73,
82
+ ]);
83
+ /** `sha256("global:register_revealed_tokenized")[0..8]`. */
84
+ export const REGISTER_REVEALED_TOKENIZED_DISCRIMINATOR = Uint8Array.from([
85
+ 46, 36, 18, 80, 132, 100, 252, 126,
86
+ ]);
87
+ /** Compute-unit limit for any transaction carrying `mint_handle_nft` /
88
+ * `mint_handle_nft_with_uri` (measured ~318k CU on mainnet; ~41% headroom). */
89
+ export const MINT_HANDLE_NFT_CU_LIMIT = 450_000;
90
+ /** Compute-unit limit for any transaction carrying `register_tokenized` /
91
+ * `register_revealed_tokenized` (measured 299k–347k CU live; ≥17% headroom). */
92
+ export const REGISTER_TOKENIZED_CU_LIMIT = 420_000;
93
+ /** `mpl_token_metadata::MAX_URI_LENGTH` — `mint_handle_nft_with_uri` refuses
94
+ * (`UriTooLong`) a longer URI; checked client-side too. */
95
+ export const MAX_NFT_METADATA_URI_LENGTH = 200;
96
+ /** Seed prefix of a handle's NFT mint PDA: `["nft_mint", handle]`. */
97
+ export const NFT_MINT_SEED = "nft_mint";
98
+ /** Seed of the collection parent's mint PDA: `["collection_mint"]`. */
99
+ export const COLLECTION_MINT_SEED = "collection_mint";
100
+ /** Seed of the collection update-authority PDA: `["collection_authority"]`. */
101
+ export const COLLECTION_AUTHORITY_SEED = "collection_authority";
102
+ const SYSTEM_PROGRAM = "11111111111111111111111111111111";
103
+ const SYSVAR_INSTRUCTIONS = "Sysvar1nstructions1111111111111111111111111";
104
+ const SPL_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
105
+ const SPL_ATA_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
106
+ const TE = new TextEncoder();
107
+ function toBytes32(v, what) {
108
+ if (typeof v === "string") {
109
+ const b = decodeBase58_32(v);
110
+ if (!b)
111
+ throw new Error(`${what} is not a valid base58 address`);
112
+ return b;
113
+ }
114
+ if (v.length !== 32)
115
+ throw new Error(`${what} must be exactly 32 bytes`);
116
+ return v;
117
+ }
118
+ function toBase58(v, what) {
119
+ return encodeBase58(toBytes32(v, what));
120
+ }
121
+ function checkHandleType(t) {
122
+ if (!Number.isInteger(t) || t < 0 || t > 3) {
123
+ throw new Error("handleType must be 0 (Human), 1 (Merchant), 2 (Org) or 3 (Agent)");
124
+ }
125
+ }
126
+ /** disc ‖ borsh String ‖ extra */
127
+ function encodeNameIx(disc, name, tail) {
128
+ const nameBytes = TE.encode(name);
129
+ if (nameBytes.length < 1 || nameBytes.length > 32) {
130
+ throw new Error("name must be 1..=32 bytes (UTF-8)");
131
+ }
132
+ const data = new Uint8Array(8 + 4 + nameBytes.length + tail.length);
133
+ data.set(disc, 0);
134
+ new DataView(data.buffer).setUint32(8, nameBytes.length, true);
135
+ data.set(nameBytes, 12);
136
+ data.set(tail, 12 + nameBytes.length);
137
+ return data;
138
+ }
139
+ function integratorKeys(p) {
140
+ const has = p.integrator !== undefined || p.integratorAllowlist !== undefined;
141
+ if (!has)
142
+ return [];
143
+ if (p.integrator === undefined || p.integratorAllowlist === undefined) {
144
+ throw new Error("integrator and integratorAllowlist must both be supplied, or neither");
145
+ }
146
+ return [
147
+ { pubkey: toBase58(p.integrator, "integrator"), isSigner: false, isWritable: true },
148
+ { pubkey: toBase58(p.integratorAllowlist, "integratorAllowlist"), isSigner: false, isWritable: false },
149
+ ];
150
+ }
151
+ /**
152
+ * Derive every PDA/ATA a tokenized REGISTER (`register_tokenized` /
153
+ * `register_revealed_tokenized`) needs beyond payer/owner/handle/treasury.
154
+ * Pure: nothing is fetched. `Config.treasury` is NOT derivable — read it with
155
+ * `fetchConfigTreasury` (register.ts).
156
+ */
157
+ export async function deriveTokenizedNftAccounts(find, p) {
158
+ const programId = toBase58(p.programId, "programId");
159
+ const owner = toBytes32(p.owner, "owner");
160
+ const handle = toBytes32(p.handle, "handle");
161
+ const mpl = toBytes32(MPL_TOKEN_METADATA_PROGRAM, "tokenMetadataProgram");
162
+ const tokenProgram = toBytes32(SPL_TOKEN_PROGRAM_ID, "tokenProgram");
163
+ const metadataOf = (m) => find([TE.encode("metadata"), mpl, m], MPL_TOKEN_METADATA_PROGRAM);
164
+ const editionOf = (m) => find([TE.encode("metadata"), mpl, m, TE.encode("edition")], MPL_TOKEN_METADATA_PROGRAM);
165
+ const [config, mint, collectionMint, collectionAuthority] = await Promise.all([
166
+ find([TE.encode("config")], programId),
167
+ find([TE.encode(NFT_MINT_SEED), handle], programId),
168
+ find([TE.encode(COLLECTION_MINT_SEED)], programId),
169
+ find([TE.encode(COLLECTION_AUTHORITY_SEED)], programId),
170
+ ]);
171
+ const mintB = toBytes32(mint, "mint");
172
+ const collB = toBytes32(collectionMint, "collectionMint");
173
+ const [ownerTokenAccount, metadata, masterEdition, collectionMetadata, collectionMasterEdition] = await Promise.all([
174
+ find([owner, tokenProgram, mintB], SPL_ATA_PROGRAM_ID),
175
+ metadataOf(mintB),
176
+ editionOf(mintB),
177
+ metadataOf(collB),
178
+ editionOf(collB),
179
+ ]);
180
+ const tokenRecord = await find([TE.encode("metadata"), mpl, mintB, TE.encode("token_record"), toBytes32(ownerTokenAccount, "ownerTokenAccount")], MPL_TOKEN_METADATA_PROGRAM);
181
+ return {
182
+ config,
183
+ mint,
184
+ ownerTokenAccount,
185
+ metadata,
186
+ masterEdition,
187
+ tokenRecord,
188
+ collectionMint,
189
+ collectionMetadata,
190
+ collectionMasterEdition,
191
+ collectionAuthority,
192
+ };
193
+ }
194
+ /**
195
+ * Derive every PDA/ATA `mint_handle_nft` needs beyond payer/owner/handle/
196
+ * treasury: {@link deriveTokenizedNftAccounts} plus `["auction", handle]` and
197
+ * `["listing", handle]`. Pure: nothing is fetched.
198
+ */
199
+ export async function deriveMintHandleNftAccounts(find, p) {
200
+ const programId = toBase58(p.programId, "programId");
201
+ const handle = toBytes32(p.handle, "handle");
202
+ const [base, auction, listing] = await Promise.all([
203
+ deriveTokenizedNftAccounts(find, p),
204
+ find([TE.encode("auction"), handle], programId),
205
+ find([TE.encode("listing"), handle], programId),
206
+ ]);
207
+ return { ...base, auction, listing };
208
+ }
209
+ /**
210
+ * The 14-account pNFT tail shared by every tokenized instruction, from
211
+ * `mint` through `sysvar_instructions` (order + signer/writable flags exactly
212
+ * as `MintHandleNft` / `RegisterTokenized` declare them in lib.rs). Exposed
213
+ * for instructions that append the same tail to their own preamble.
214
+ */
215
+ export function buildTokenizedTailKeys(a) {
216
+ return [
217
+ { pubkey: toBase58(a.mint, "mint"), isSigner: false, isWritable: true },
218
+ { pubkey: toBase58(a.ownerTokenAccount, "ownerTokenAccount"), isSigner: false, isWritable: true },
219
+ { pubkey: toBase58(a.metadata, "metadata"), isSigner: false, isWritable: true },
220
+ { pubkey: toBase58(a.masterEdition, "masterEdition"), isSigner: false, isWritable: true },
221
+ { pubkey: toBase58(a.tokenRecord, "tokenRecord"), isSigner: false, isWritable: true },
222
+ { pubkey: toBase58(a.collectionMint, "collectionMint"), isSigner: false, isWritable: false },
223
+ { pubkey: toBase58(a.collectionMetadata, "collectionMetadata"), isSigner: false, isWritable: true },
224
+ { pubkey: toBase58(a.collectionMasterEdition, "collectionMasterEdition"), isSigner: false, isWritable: false },
225
+ { pubkey: toBase58(a.collectionAuthority, "collectionAuthority"), isSigner: false, isWritable: false },
226
+ { pubkey: MPL_TOKEN_METADATA_PROGRAM, isSigner: false, isWritable: false },
227
+ { pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
228
+ { pubkey: SPL_ATA_PROGRAM_ID, isSigner: false, isWritable: false },
229
+ { pubkey: SYSTEM_PROGRAM, isSigner: false, isWritable: false },
230
+ { pubkey: SYSVAR_INSTRUCTIONS, isSigner: false, isWritable: false },
231
+ ];
232
+ }
233
+ function mintHandleNftKeys(p) {
234
+ return [
235
+ { pubkey: toBase58(p.payer, "payer"), isSigner: true, isWritable: true },
236
+ { pubkey: toBase58(p.owner, "owner"), isSigner: true, isWritable: false },
237
+ { pubkey: toBase58(p.config, "config"), isSigner: false, isWritable: true },
238
+ { pubkey: toBase58(p.treasury, "treasury"), isSigner: false, isWritable: true },
239
+ { pubkey: toBase58(p.handle, "handle"), isSigner: false, isWritable: true },
240
+ { pubkey: toBase58(p.auction, "auction"), isSigner: false, isWritable: false },
241
+ { pubkey: toBase58(p.listing, "listing"), isSigner: false, isWritable: false },
242
+ ...buildTokenizedTailKeys(p),
243
+ ];
244
+ }
245
+ /**
246
+ * Build `mint_handle_nft` — tokenize an ALREADY-REGISTERED, untokenized
247
+ * handle as its capability pNFT. Signers: `payer` AND `owner` (two signature
248
+ * slots unless they are the same key). Account order: `MintHandleNft<'info>`
249
+ * (see module docs). Data is the 8-byte discriminator only. Prepend
250
+ * `SetComputeUnitLimit(MINT_HANDLE_NFT_CU_LIMIT)`.
251
+ */
252
+ export function buildMintHandleNftIx(p) {
253
+ return {
254
+ programId: toBase58(p.programId, "programId"),
255
+ keys: mintHandleNftKeys(p),
256
+ data: MINT_HANDLE_NFT_DISCRIMINATOR.slice(),
257
+ };
258
+ }
259
+ /**
260
+ * Build `mint_handle_nft_with_uri` — {@link buildMintHandleNftIx} with a
261
+ * caller-supplied Metaplex `uri` instead of the program's hosted one. Same
262
+ * accounts and signers; data = discriminator ‖ borsh(String metadataUri).
263
+ */
264
+ export function buildMintHandleNftWithUriIx(p) {
265
+ const uri = TE.encode(p.metadataUri);
266
+ if (uri.length > MAX_NFT_METADATA_URI_LENGTH) {
267
+ throw new Error(`Metadata URI exceeds ${MAX_NFT_METADATA_URI_LENGTH} bytes.`);
268
+ }
269
+ const data = new Uint8Array(8 + 4 + uri.length);
270
+ data.set(MINT_HANDLE_NFT_WITH_URI_DISCRIMINATOR, 0);
271
+ new DataView(data.buffer).setUint32(8, uri.length, true);
272
+ data.set(uri, 12);
273
+ return { programId: toBase58(p.programId, "programId"), keys: mintHandleNftKeys(p), data };
274
+ }
275
+ /**
276
+ * Build `register_tokenized` — register a new handle AND mint its pNFT in
277
+ * one instruction (the default permanent-XNT purchase). Signer: `payer`
278
+ * only. Account order: `RegisterTokenized<'info>` (see module docs). Data =
279
+ * discriminator ‖ borsh(name) ‖ handle_type(u8). Price is computed on-chain
280
+ * (as for `register`). Prepend `SetComputeUnitLimit(REGISTER_TOKENIZED_CU_LIMIT)`.
281
+ * `name` must be canonical (the PDA seeds on it).
282
+ */
283
+ export function buildRegisterTokenizedIx(p) {
284
+ checkHandleType(p.handleType);
285
+ const canonical = normalizeHandle(p.name);
286
+ if (canonical !== p.name) {
287
+ throw new Error(`name must be the canonical handle "${canonical}" (got "${p.name}") — normalizeHandle() it and derive \`handle\` from the same value`);
288
+ }
289
+ const data = encodeNameIx(REGISTER_TOKENIZED_DISCRIMINATOR, p.name, Uint8Array.of(p.handleType));
290
+ const keys = [
291
+ { pubkey: toBase58(p.payer, "payer"), isSigner: true, isWritable: true },
292
+ { pubkey: toBase58(p.owner, "owner"), isSigner: false, isWritable: false },
293
+ { pubkey: toBase58(p.config, "config"), isSigner: false, isWritable: true },
294
+ { pubkey: toBase58(p.treasury, "treasury"), isSigner: false, isWritable: true },
295
+ { pubkey: toBase58(p.handle, "handle"), isSigner: false, isWritable: true },
296
+ ...buildTokenizedTailKeys(p),
297
+ ...integratorKeys(p),
298
+ ];
299
+ return { programId: toBase58(p.programId, "programId"), keys, data };
300
+ }
301
+ /**
302
+ * Build `register_revealed_tokenized` — the commit-reveal sibling of
303
+ * {@link buildRegisterTokenizedIx}. Signer: `payer` only. Same accounts plus
304
+ * `commitment` (w) right after `handle`; data = discriminator ‖ borsh(name)
305
+ * ‖ handle_type(u8) ‖ secret[32]. Prepend
306
+ * `SetComputeUnitLimit(REGISTER_TOKENIZED_CU_LIMIT)`.
307
+ */
308
+ export function buildRegisterRevealedTokenizedIx(p) {
309
+ checkHandleType(p.handleType);
310
+ if (!(p.secret instanceof Uint8Array) || p.secret.length !== 32) {
311
+ throw new Error("secret must be exactly 32 bytes");
312
+ }
313
+ const tail = new Uint8Array(33);
314
+ tail[0] = p.handleType;
315
+ tail.set(p.secret, 1);
316
+ const data = encodeNameIx(REGISTER_REVEALED_TOKENIZED_DISCRIMINATOR, p.name, tail);
317
+ const keys = [
318
+ { pubkey: toBase58(p.payer, "payer"), isSigner: true, isWritable: true },
319
+ { pubkey: toBase58(p.owner, "owner"), isSigner: false, isWritable: false },
320
+ { pubkey: toBase58(p.config, "config"), isSigner: false, isWritable: true },
321
+ { pubkey: toBase58(p.treasury, "treasury"), isSigner: false, isWritable: true },
322
+ { pubkey: toBase58(p.handle, "handle"), isSigner: false, isWritable: true },
323
+ { pubkey: toBase58(p.commitment, "commitment"), isSigner: false, isWritable: true },
324
+ ...buildTokenizedTailKeys(p),
325
+ ...integratorKeys(p),
326
+ ];
327
+ return { programId: toBase58(p.programId, "programId"), keys, data };
328
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x1id/resolve",
3
- "version": "0.22.0",
3
+ "version": "0.23.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",