@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 +1 -0
- package/dist/index.js +1 -0
- package/dist/mintNft.d.ts +247 -0
- package/dist/mintNft.js +328 -0
- package/package.json +1 -1
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;
|
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
|
+
}
|