@x1id/resolve 0.21.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/README.md +3 -1
- package/dist/adminConfig.d.ts +17 -3
- package/dist/adminConfig.js +24 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mintNft.d.ts +247 -0
- package/dist/mintNft.js +328 -0
- package/dist/namespacePricing.d.ts +207 -0
- package/dist/namespacePricing.js +378 -0
- package/dist/sha256.d.ts +10 -0
- package/dist/sha256.js +77 -0
- package/dist/voucher.d.ts +25 -7
- package/dist/voucher.js +23 -13
- package/dist/x402.d.ts +70 -6
- package/dist/x402.js +158 -7
- package/package.json +1 -1
package/dist/mintNft.js
ADDED
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TLD creator pricing (docs/specs/tld-creator-pricing.md): the SDK side of a
|
|
3
|
+
* TLD creator's own price table for names under their TLD.
|
|
4
|
+
*
|
|
5
|
+
* - `["ns_pricing", label]` holds a `NamespacePricing` account: a flat lamport
|
|
6
|
+
* price per length band (index = `tier_index(len)`: 0 = 1 char … 8 = 9 chars,
|
|
7
|
+
* 9 = 10+), written ONLY by the namespace creator via `set_namespace_pricing`.
|
|
8
|
+
* - `register_under_namespace_v2` (v1's accounts + the pricing PDA as the
|
|
9
|
+
* first remaining account) charges that band FLAT (no launch ramp) when
|
|
10
|
+
* the account exists, else the legacy root price (`Config.tier_lamports` ×
|
|
11
|
+
* the time/volume ramp), and refuses to charge more than the buyer's
|
|
12
|
+
* `max_price_lamports`.
|
|
13
|
+
* - The legacy `register_under_namespace` (v1) fails closed
|
|
14
|
+
* (`NamespacePricingRequiresV2`, 6117) on any TLD whose creator opted in.
|
|
15
|
+
*
|
|
16
|
+
* {@link quoteScopedName} reproduces the program's price selection exactly, so
|
|
17
|
+
* pass its `lamports` as `maxPriceLamports` to {@link buildRegisterUnderNamespaceV2Ix}.
|
|
18
|
+
*
|
|
19
|
+
* Hand-rolled like the rest of this package: no Anchor client, no
|
|
20
|
+
* `@solana/web3.js` import, no `@types/node`. PDAs are derived through an
|
|
21
|
+
* INJECTED `find_program_address` (see {@link PdaFinder}) because this package
|
|
22
|
+
* never hand-rolls the ed25519 on-curve check.
|
|
23
|
+
*/
|
|
24
|
+
import type { AddressLike, BuiltInstruction } from "./delegate.js";
|
|
25
|
+
import type { RpcFn } from "./accounts.js";
|
|
26
|
+
/** Seed prefix of the `["ns_pricing", label]` PDA. */
|
|
27
|
+
export declare const NAMESPACE_PRICING_SEED = "ns_pricing";
|
|
28
|
+
/** `NAMESPACE_TIER_FLOOR_LAMPORTS`: every creator tier must be >= 1 XNT. */
|
|
29
|
+
export declare const NAMESPACE_TIER_FLOOR_LAMPORTS = 1000000000n;
|
|
30
|
+
/** `NAMESPACE_TIER_MAX_LAMPORTS`: every creator tier must be <= 100,000,000 XNT. */
|
|
31
|
+
export declare const NAMESPACE_TIER_MAX_LAMPORTS: bigint;
|
|
32
|
+
/** Number of length bands (same banding as the root `Config.tier_lamports`). */
|
|
33
|
+
export declare const NAMESPACE_TIER_COUNT = 10;
|
|
34
|
+
/** `sha256("global:set_namespace_pricing")[0..8]`. */
|
|
35
|
+
export declare const SET_NAMESPACE_PRICING_DISCRIMINATOR: Uint8Array;
|
|
36
|
+
/** `sha256("global:register_under_namespace_v2")[0..8]`. */
|
|
37
|
+
export declare const REGISTER_UNDER_NAMESPACE_V2_DISCRIMINATOR: Uint8Array;
|
|
38
|
+
/** `sha256("account:NamespacePricing")[0..8]`. */
|
|
39
|
+
export declare const NAMESPACE_PRICING_ACCOUNT_DISCRIMINATOR: Uint8Array;
|
|
40
|
+
/** `Namespace::PRICING_FLAG_EXT_OFFSET` — the one-byte "creator pricing is on"
|
|
41
|
+
* marker past the Anchor-allocated `Namespace` (8 + INIT_SPACE = 107). */
|
|
42
|
+
export declare const NAMESPACE_PRICING_MARKER_OFFSET = 107;
|
|
43
|
+
/** Program error codes added by this feature (6000 + variant index). */
|
|
44
|
+
export declare const NAMESPACE_PRICING_ERRORS: {
|
|
45
|
+
readonly NamespacePricingMismatch: 6116;
|
|
46
|
+
readonly NamespacePricingRequiresV2: 6117;
|
|
47
|
+
readonly PriceExceedsMax: 6118;
|
|
48
|
+
readonly NamespaceTierBelowFloor: 6119;
|
|
49
|
+
readonly NamespaceTierAboveMax: 6120;
|
|
50
|
+
};
|
|
51
|
+
/** lib.rs `tier_index`: 1 char -> 0 … 10+ -> 9 (0 clamps to 0). */
|
|
52
|
+
export declare function scopedTierIndex(len: number): number;
|
|
53
|
+
/** lib.rs `price_now` (registration price), in bigint like the program's u128. */
|
|
54
|
+
export declare function rootPriceNow(tierLamports: bigint, launchTs: bigint, nowSecs: bigint, totalHandlesSold: bigint): bigint;
|
|
55
|
+
/** Problems with a creator tier table, or null when the program would accept
|
|
56
|
+
* it (exactly `validate_namespace_tiers`). */
|
|
57
|
+
export declare function namespaceTiersProblem(tiers: ReadonlyArray<bigint>): string | null;
|
|
58
|
+
/** `find_program_address` — injected so this package never does curve math
|
|
59
|
+
* itself. With web3.js: `(seeds, pid) => PublicKey.findProgramAddressSync(seeds, new PublicKey(pid))[0].toBase58()`. */
|
|
60
|
+
export type PdaFinder = (seeds: Uint8Array[], programId: string) => Promise<string> | string;
|
|
61
|
+
/** The seeds of `["ns_pricing", label]`. */
|
|
62
|
+
export declare function namespacePricingSeeds(label: string): Uint8Array[];
|
|
63
|
+
/** Derive the canonical `["ns_pricing", label]` PDA. */
|
|
64
|
+
export declare function deriveNamespacePricingPda(label: string, programId: AddressLike, find: PdaFinder): Promise<string>;
|
|
65
|
+
/** Decoded `NamespacePricing` account. */
|
|
66
|
+
export interface NamespacePricing {
|
|
67
|
+
readonly version: number;
|
|
68
|
+
readonly bump: number;
|
|
69
|
+
/** Lamports per band, index = {@link scopedTierIndex}(len). */
|
|
70
|
+
readonly tierLamports: readonly bigint[];
|
|
71
|
+
readonly updatedAt: bigint;
|
|
72
|
+
readonly label: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Decode a `NamespacePricing` account's raw data (discriminator included):
|
|
76
|
+
* disc(8) | version(u8) | bump(u8) | tier_lamports([u64;10]) | updated_at(i64)
|
|
77
|
+
* | label(String). Returns null when the bytes are not a `NamespacePricing`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function decodeNamespacePricing(data: Uint8Array): NamespacePricing | null;
|
|
80
|
+
/** Whether a raw `Namespace` account carries the "creator pricing is on"
|
|
81
|
+
* marker (any nonzero byte at offset 107; a shorter account = off). */
|
|
82
|
+
export declare function namespaceHasPricingMarker(namespaceData: Uint8Array): boolean;
|
|
83
|
+
export interface SetNamespacePricingParams {
|
|
84
|
+
readonly programId: AddressLike;
|
|
85
|
+
/** The namespace `creator` (signer, pays rent on first set). */
|
|
86
|
+
readonly creator: AddressLike;
|
|
87
|
+
/** `["namespace", label]`. */
|
|
88
|
+
readonly namespace: AddressLike;
|
|
89
|
+
/** `["ns_pricing", label]` ({@link deriveNamespacePricingPda}). */
|
|
90
|
+
readonly namespacePricing: AddressLike;
|
|
91
|
+
readonly label: string;
|
|
92
|
+
/** 10 lamport prices, index = {@link scopedTierIndex}(len). */
|
|
93
|
+
readonly tierLamports: ReadonlyArray<bigint>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build `set_namespace_pricing(label, tier_lamports)`. Accounts IN ORDER
|
|
97
|
+
* (`SetNamespacePricing`): creator(s,w), namespace(w), namespace_pricing(w),
|
|
98
|
+
* system_program. Validates the table client-side with the program's exact
|
|
99
|
+
* bounds so a bad table never reaches a wallet prompt.
|
|
100
|
+
*/
|
|
101
|
+
export declare function buildSetNamespacePricingIx(p: SetNamespacePricingParams): BuiltInstruction;
|
|
102
|
+
export interface RegisterUnderNamespaceV2Params {
|
|
103
|
+
readonly programId: AddressLike;
|
|
104
|
+
/** `["ns_pricing", label]` — ALWAYS the canonical PDA, whether or not the
|
|
105
|
+
* creator has set pricing (the program decides from its contents). */
|
|
106
|
+
readonly namespacePricing: AddressLike;
|
|
107
|
+
readonly payer: AddressLike;
|
|
108
|
+
readonly owner: AddressLike;
|
|
109
|
+
readonly config: AddressLike;
|
|
110
|
+
readonly treasury: AddressLike;
|
|
111
|
+
readonly namespace: AddressLike;
|
|
112
|
+
readonly creatorPayout: AddressLike;
|
|
113
|
+
/** `["handle", [label_len], label, name]`. */
|
|
114
|
+
readonly handle: AddressLike;
|
|
115
|
+
readonly mint: AddressLike;
|
|
116
|
+
readonly ownerTokenAccount: AddressLike;
|
|
117
|
+
readonly metadata: AddressLike;
|
|
118
|
+
readonly masterEdition: AddressLike;
|
|
119
|
+
readonly tokenRecord: AddressLike;
|
|
120
|
+
readonly collectionMint: AddressLike;
|
|
121
|
+
readonly collectionMetadata: AddressLike;
|
|
122
|
+
readonly collectionMasterEdition: AddressLike;
|
|
123
|
+
readonly collectionAuthority: AddressLike;
|
|
124
|
+
/** Canonical sub-name (`alice` in `alice.pepe`). */
|
|
125
|
+
readonly name: string;
|
|
126
|
+
/** 0 Human, 1 Merchant, 2 Org, 3 Agent. */
|
|
127
|
+
readonly handleType: number;
|
|
128
|
+
/** Buyer's ceiling: the tx fails (`PriceExceedsMax`) if the on-chain price
|
|
129
|
+
* is higher. Pass {@link quoteScopedName}'s `lamports`. */
|
|
130
|
+
readonly maxPriceLamports: bigint;
|
|
131
|
+
/** OPTIONAL referral pair — both or neither. */
|
|
132
|
+
readonly integrator?: AddressLike;
|
|
133
|
+
readonly integratorAllowlist?: AddressLike;
|
|
134
|
+
/** OPTIONAL `["ns_minter", label, payer]` (invite-only, non-creator). */
|
|
135
|
+
readonly minterAuthorization?: AddressLike;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build `register_under_namespace_v2(name, handle_type, max_price_lamports)`.
|
|
139
|
+
* Accounts IN ORDER: the exact v1 `RegisterUnderNamespace` list (payer(s,w),
|
|
140
|
+
* owner, config(w), treasury(w), namespace, creator_payout(w), handle(w),
|
|
141
|
+
* mint(w), owner_token_account(w), metadata(w), master_edition(w),
|
|
142
|
+
* token_record(w), collection_mint, collection_metadata(w),
|
|
143
|
+
* collection_master_edition, collection_authority, token_metadata_program,
|
|
144
|
+
* token_program, associated_token_program, system_program,
|
|
145
|
+
* sysvar_instructions), then the three optional slots ALWAYS present
|
|
146
|
+
* (integrator, integrator_allowlist, minter_authorization — each the program
|
|
147
|
+
* id when absent, Anchor's "explicitly None"), then `namespace_pricing` as
|
|
148
|
+
* `remaining_accounts[0]`. The program fails closed if it is missing or
|
|
149
|
+
* shifted.
|
|
150
|
+
*/
|
|
151
|
+
export declare function buildRegisterUnderNamespaceV2Ix(p: RegisterUnderNamespaceV2Params): BuiltInstruction;
|
|
152
|
+
/** The program's root pricing inputs, read from `Config`. */
|
|
153
|
+
export interface RootPricingInputs {
|
|
154
|
+
readonly launchTs: bigint;
|
|
155
|
+
readonly tierLamports: readonly bigint[];
|
|
156
|
+
readonly totalHandlesSold: bigint;
|
|
157
|
+
}
|
|
158
|
+
/** Read `launch_ts`, `tier_lamports` and `total_handles_sold` from raw Config bytes. */
|
|
159
|
+
export declare function decodeRootPricingInputs(config: Uint8Array): RootPricingInputs | null;
|
|
160
|
+
/**
|
|
161
|
+
* The price `register_under_namespace_v2` charges, from already-read state —
|
|
162
|
+
* `scoped_registration_price` in lib.rs: the creator's band flat when a table
|
|
163
|
+
* exists, else the root band × the ramp.
|
|
164
|
+
*/
|
|
165
|
+
export declare function scopedRegistrationPrice(pricing: NamespacePricing | null, root: RootPricingInputs, nameLen: number, nowSecs: bigint): bigint;
|
|
166
|
+
export interface ScopedQuote {
|
|
167
|
+
/** Lamports `register_under_namespace_v2` will charge (pass as `maxPriceLamports`). */
|
|
168
|
+
readonly lamports: bigint;
|
|
169
|
+
/** `custom` = the creator's table; `fallback` = root tiers × ramp. */
|
|
170
|
+
readonly source: "custom" | "fallback";
|
|
171
|
+
readonly tierIndex: number;
|
|
172
|
+
/** The canonical `["ns_pricing", label]` PDA (pass to the v2 builder). */
|
|
173
|
+
readonly pricingAccount: string;
|
|
174
|
+
/** The creator's full table when `source === "custom"`. */
|
|
175
|
+
readonly pricing: NamespacePricing | null;
|
|
176
|
+
}
|
|
177
|
+
export interface QuoteScopedNameOptions {
|
|
178
|
+
readonly rpc: RpcFn;
|
|
179
|
+
readonly programId: AddressLike;
|
|
180
|
+
readonly find: PdaFinder;
|
|
181
|
+
/** Unix seconds; defaults to the local clock (the ramp moves per quarter, so
|
|
182
|
+
* seconds of skew never matter for a fresh quote). */
|
|
183
|
+
readonly nowSecs?: bigint;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Quote `name.label` exactly like the program prices it (pricing-or-fallback).
|
|
187
|
+
* Throws when Config is missing, or when the pricing account exists but is not
|
|
188
|
+
* a valid `NamespacePricing` owned by the program (the program would refuse
|
|
189
|
+
* too), so a caller never shows a price the chain will not honour.
|
|
190
|
+
*/
|
|
191
|
+
export declare function quoteScopedName(label: string, name: string, opts: QuoteScopedNameOptions): Promise<ScopedQuote>;
|
|
192
|
+
/**
|
|
193
|
+
* The instruction to SIMULATE (never send) to learn whether the deployed
|
|
194
|
+
* program has this feature: `set_namespace_pricing` with no accounts. An
|
|
195
|
+
* upgraded program recognizes the discriminator and fails account validation
|
|
196
|
+
* (e.g. 3005 `AccountNotEnoughKeys`); a pre-upgrade program fails with
|
|
197
|
+
* Anchor's `InstructionFallbackNotFound` (101). Interpret the simulation's
|
|
198
|
+
* `err` with {@link interpretNamespacePricingProbe}.
|
|
199
|
+
*/
|
|
200
|
+
export declare function buildNamespacePricingProbeIx(programId: AddressLike): BuiltInstruction;
|
|
201
|
+
/**
|
|
202
|
+
* Read a `simulateTransaction` result's `value.err` for the probe tx (whose
|
|
203
|
+
* probe instruction is at `probeIndex`). `true` = the program knows the
|
|
204
|
+
* instruction; `false` = it does not, or the result is not the shape we
|
|
205
|
+
* expect (fail closed: hide the feature rather than show a broken button).
|
|
206
|
+
*/
|
|
207
|
+
export declare function interpretNamespacePricingProbe(err: unknown, probeIndex: number): boolean;
|