@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
|
@@ -0,0 +1,378 @@
|
|
|
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 { encodeBase58, decodeBase58_32 } from "./base58.js";
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// constants (mirror programs/x1-handles/src/{lib,state}.rs)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/** Seed prefix of the `["ns_pricing", label]` PDA. */
|
|
29
|
+
export const NAMESPACE_PRICING_SEED = "ns_pricing";
|
|
30
|
+
/** `NAMESPACE_TIER_FLOOR_LAMPORTS`: every creator tier must be >= 1 XNT. */
|
|
31
|
+
export const NAMESPACE_TIER_FLOOR_LAMPORTS = 1000000000n;
|
|
32
|
+
/** `NAMESPACE_TIER_MAX_LAMPORTS`: every creator tier must be <= 100,000,000 XNT. */
|
|
33
|
+
export const NAMESPACE_TIER_MAX_LAMPORTS = 100000000n * 1000000000n;
|
|
34
|
+
/** Number of length bands (same banding as the root `Config.tier_lamports`). */
|
|
35
|
+
export const NAMESPACE_TIER_COUNT = 10;
|
|
36
|
+
/** `sha256("global:set_namespace_pricing")[0..8]`. */
|
|
37
|
+
export const SET_NAMESPACE_PRICING_DISCRIMINATOR = Uint8Array.from([
|
|
38
|
+
245, 190, 192, 77, 94, 146, 50, 246,
|
|
39
|
+
]);
|
|
40
|
+
/** `sha256("global:register_under_namespace_v2")[0..8]`. */
|
|
41
|
+
export const REGISTER_UNDER_NAMESPACE_V2_DISCRIMINATOR = Uint8Array.from([
|
|
42
|
+
233, 132, 213, 179, 60, 113, 18, 247,
|
|
43
|
+
]);
|
|
44
|
+
/** `sha256("account:NamespacePricing")[0..8]`. */
|
|
45
|
+
export const NAMESPACE_PRICING_ACCOUNT_DISCRIMINATOR = Uint8Array.from([
|
|
46
|
+
197, 197, 26, 252, 150, 176, 211, 188,
|
|
47
|
+
]);
|
|
48
|
+
/** `Namespace::PRICING_FLAG_EXT_OFFSET` — the one-byte "creator pricing is on"
|
|
49
|
+
* marker past the Anchor-allocated `Namespace` (8 + INIT_SPACE = 107). */
|
|
50
|
+
export const NAMESPACE_PRICING_MARKER_OFFSET = 107;
|
|
51
|
+
/** Program error codes added by this feature (6000 + variant index). */
|
|
52
|
+
export const NAMESPACE_PRICING_ERRORS = {
|
|
53
|
+
NamespacePricingMismatch: 6116,
|
|
54
|
+
NamespacePricingRequiresV2: 6117,
|
|
55
|
+
PriceExceedsMax: 6118,
|
|
56
|
+
NamespaceTierBelowFloor: 6119,
|
|
57
|
+
NamespaceTierAboveMax: 6120,
|
|
58
|
+
};
|
|
59
|
+
/** Anchor's `InstructionFallbackNotFound`: the deployed program does not know
|
|
60
|
+
* the instruction (i.e. it predates this feature). */
|
|
61
|
+
const ANCHOR_INSTRUCTION_FALLBACK_NOT_FOUND = 101;
|
|
62
|
+
// Root pricing curve — mirrors lib.rs `ramp_bps` / `quarters_elapsed` /
|
|
63
|
+
// `compute_price` / `price_now` exactly (same constants as app/src/lib/x1/pricing.ts).
|
|
64
|
+
const INTRO_START_BPS = 5000n;
|
|
65
|
+
const INTRO_STEP_BPS = 1250n;
|
|
66
|
+
const QUARTER_SECS = 91n * 24n * 60n * 60n;
|
|
67
|
+
const VOLUME_START_BPS = 5000n;
|
|
68
|
+
const VOLUME_STEP_BPS = 1250n;
|
|
69
|
+
const VOLUME_STEP_COUNT = 500n;
|
|
70
|
+
// Config byte offsets (state.rs): disc(8) admin(32) treasury(32) launch_ts(8)
|
|
71
|
+
// tier_lamports(80) nft_tier_lamports(80) total_handles_sold(8) ...
|
|
72
|
+
const CONFIG_LAUNCH_TS_OFFSET = 72;
|
|
73
|
+
const CONFIG_TIERS_OFFSET = 80;
|
|
74
|
+
const CONFIG_TOTAL_SOLD_OFFSET = 240;
|
|
75
|
+
const SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
76
|
+
const TE = new TextEncoder();
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// pure helpers
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
function toBytes32(v, what) {
|
|
81
|
+
if (typeof v === "string") {
|
|
82
|
+
const b = decodeBase58_32(v);
|
|
83
|
+
if (!b)
|
|
84
|
+
throw new Error(`${what} is not a valid base58 address`);
|
|
85
|
+
return b;
|
|
86
|
+
}
|
|
87
|
+
if (v.length !== 32)
|
|
88
|
+
throw new Error(`${what} must be exactly 32 bytes`);
|
|
89
|
+
return v;
|
|
90
|
+
}
|
|
91
|
+
function toBase58(v, what) {
|
|
92
|
+
return encodeBase58(toBytes32(v, what));
|
|
93
|
+
}
|
|
94
|
+
function key(pubkey, what, isSigner, isWritable) {
|
|
95
|
+
return { pubkey: toBase58(pubkey, what), isSigner, isWritable };
|
|
96
|
+
}
|
|
97
|
+
function borshString(s) {
|
|
98
|
+
const b = TE.encode(s);
|
|
99
|
+
const out = new Uint8Array(4 + b.length);
|
|
100
|
+
new DataView(out.buffer).setUint32(0, b.length, true);
|
|
101
|
+
out.set(b, 4);
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function u64le(n) {
|
|
105
|
+
const out = new Uint8Array(8);
|
|
106
|
+
new DataView(out.buffer).setBigUint64(0, n, true);
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function concat(...parts) {
|
|
110
|
+
const out = new Uint8Array(parts.reduce((a, p) => a + p.length, 0));
|
|
111
|
+
let o = 0;
|
|
112
|
+
for (const p of parts) {
|
|
113
|
+
out.set(p, o);
|
|
114
|
+
o += p.length;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/** lib.rs `tier_index`: 1 char -> 0 … 10+ -> 9 (0 clamps to 0). */
|
|
119
|
+
export function scopedTierIndex(len) {
|
|
120
|
+
return Math.min(Math.max(len, 1), 10) - 1;
|
|
121
|
+
}
|
|
122
|
+
/** lib.rs `price_now` (registration price), in bigint like the program's u128. */
|
|
123
|
+
export function rootPriceNow(tierLamports, launchTs, nowSecs, totalHandlesSold) {
|
|
124
|
+
const elapsed = nowSecs > launchTs ? nowSecs - launchTs : 0n;
|
|
125
|
+
const quarters = elapsed / QUARTER_SECS;
|
|
126
|
+
const ramp = (start, step, n) => {
|
|
127
|
+
const v = start + step * n;
|
|
128
|
+
return v > 10000n ? 10000n : v;
|
|
129
|
+
};
|
|
130
|
+
const timeBps = ramp(INTRO_START_BPS, INTRO_STEP_BPS, quarters);
|
|
131
|
+
const volumeBps = ramp(VOLUME_START_BPS, VOLUME_STEP_BPS, totalHandlesSold / VOLUME_STEP_COUNT);
|
|
132
|
+
return (tierLamports * timeBps * volumeBps) / 10000n / 10000n;
|
|
133
|
+
}
|
|
134
|
+
/** Problems with a creator tier table, or null when the program would accept
|
|
135
|
+
* it (exactly `validate_namespace_tiers`). */
|
|
136
|
+
export function namespaceTiersProblem(tiers) {
|
|
137
|
+
if (tiers.length !== NAMESPACE_TIER_COUNT)
|
|
138
|
+
return `expected ${NAMESPACE_TIER_COUNT} tiers, got ${tiers.length}`;
|
|
139
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
140
|
+
const t = tiers[i];
|
|
141
|
+
if (t < NAMESPACE_TIER_FLOOR_LAMPORTS)
|
|
142
|
+
return `tier ${i} is below the 1 XNT protocol floor`;
|
|
143
|
+
if (t > NAMESPACE_TIER_MAX_LAMPORTS)
|
|
144
|
+
return `tier ${i} is above the 100,000,000 XNT protocol maximum`;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
/** The seeds of `["ns_pricing", label]`. */
|
|
149
|
+
export function namespacePricingSeeds(label) {
|
|
150
|
+
return [TE.encode(NAMESPACE_PRICING_SEED), TE.encode(label)];
|
|
151
|
+
}
|
|
152
|
+
/** Derive the canonical `["ns_pricing", label]` PDA. */
|
|
153
|
+
export async function deriveNamespacePricingPda(label, programId, find) {
|
|
154
|
+
return find(namespacePricingSeeds(label), toBase58(programId, "programId"));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Decode a `NamespacePricing` account's raw data (discriminator included):
|
|
158
|
+
* disc(8) | version(u8) | bump(u8) | tier_lamports([u64;10]) | updated_at(i64)
|
|
159
|
+
* | label(String). Returns null when the bytes are not a `NamespacePricing`.
|
|
160
|
+
*/
|
|
161
|
+
export function decodeNamespacePricing(data) {
|
|
162
|
+
if (data.length < 8 + 1 + 1 + 80 + 8 + 4)
|
|
163
|
+
return null;
|
|
164
|
+
for (let i = 0; i < 8; i++) {
|
|
165
|
+
if (data[i] !== NAMESPACE_PRICING_ACCOUNT_DISCRIMINATOR[i])
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
169
|
+
const tierLamports = [];
|
|
170
|
+
for (let i = 0; i < NAMESPACE_TIER_COUNT; i++)
|
|
171
|
+
tierLamports.push(dv.getBigUint64(10 + 8 * i, true));
|
|
172
|
+
const updatedAt = dv.getBigInt64(90, true);
|
|
173
|
+
const labelLen = dv.getUint32(98, true);
|
|
174
|
+
if (data.length < 102 + labelLen)
|
|
175
|
+
return null;
|
|
176
|
+
let label;
|
|
177
|
+
try {
|
|
178
|
+
label = new TextDecoder("utf-8", { fatal: true }).decode(data.subarray(102, 102 + labelLen));
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return { version: data[8], bump: data[9], tierLamports, updatedAt, label };
|
|
184
|
+
}
|
|
185
|
+
/** Whether a raw `Namespace` account carries the "creator pricing is on"
|
|
186
|
+
* marker (any nonzero byte at offset 107; a shorter account = off). */
|
|
187
|
+
export function namespaceHasPricingMarker(namespaceData) {
|
|
188
|
+
const b = namespaceData[NAMESPACE_PRICING_MARKER_OFFSET];
|
|
189
|
+
return b !== undefined && b !== 0;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Build `set_namespace_pricing(label, tier_lamports)`. Accounts IN ORDER
|
|
193
|
+
* (`SetNamespacePricing`): creator(s,w), namespace(w), namespace_pricing(w),
|
|
194
|
+
* system_program. Validates the table client-side with the program's exact
|
|
195
|
+
* bounds so a bad table never reaches a wallet prompt.
|
|
196
|
+
*/
|
|
197
|
+
export function buildSetNamespacePricingIx(p) {
|
|
198
|
+
const problem = namespaceTiersProblem(p.tierLamports);
|
|
199
|
+
if (problem)
|
|
200
|
+
throw new Error(problem);
|
|
201
|
+
return {
|
|
202
|
+
programId: toBase58(p.programId, "programId"),
|
|
203
|
+
keys: [
|
|
204
|
+
key(p.creator, "creator", true, true),
|
|
205
|
+
key(p.namespace, "namespace", false, true),
|
|
206
|
+
key(p.namespacePricing, "namespacePricing", false, true),
|
|
207
|
+
key(SYSTEM_PROGRAM, "systemProgram", false, false),
|
|
208
|
+
],
|
|
209
|
+
data: concat(SET_NAMESPACE_PRICING_DISCRIMINATOR, borshString(p.label), ...p.tierLamports.map(u64le)),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
213
|
+
const ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
214
|
+
const TOKEN_METADATA_PROGRAM = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
|
|
215
|
+
const SYSVAR_INSTRUCTIONS = "Sysvar1nstructions1111111111111111111111111";
|
|
216
|
+
/**
|
|
217
|
+
* Build `register_under_namespace_v2(name, handle_type, max_price_lamports)`.
|
|
218
|
+
* Accounts IN ORDER: the exact v1 `RegisterUnderNamespace` list (payer(s,w),
|
|
219
|
+
* owner, config(w), treasury(w), namespace, creator_payout(w), handle(w),
|
|
220
|
+
* mint(w), owner_token_account(w), metadata(w), master_edition(w),
|
|
221
|
+
* token_record(w), collection_mint, collection_metadata(w),
|
|
222
|
+
* collection_master_edition, collection_authority, token_metadata_program,
|
|
223
|
+
* token_program, associated_token_program, system_program,
|
|
224
|
+
* sysvar_instructions), then the three optional slots ALWAYS present
|
|
225
|
+
* (integrator, integrator_allowlist, minter_authorization — each the program
|
|
226
|
+
* id when absent, Anchor's "explicitly None"), then `namespace_pricing` as
|
|
227
|
+
* `remaining_accounts[0]`. The program fails closed if it is missing or
|
|
228
|
+
* shifted.
|
|
229
|
+
*/
|
|
230
|
+
export function buildRegisterUnderNamespaceV2Ix(p) {
|
|
231
|
+
if (!Number.isInteger(p.handleType) || p.handleType < 0 || p.handleType > 3) {
|
|
232
|
+
throw new Error("handleType must be 0 (Human), 1 (Merchant), 2 (Org) or 3 (Agent)");
|
|
233
|
+
}
|
|
234
|
+
if (typeof p.maxPriceLamports !== "bigint" || p.maxPriceLamports < 0n || p.maxPriceLamports > 0xffffffffffffffffn) {
|
|
235
|
+
throw new Error("maxPriceLamports must be a u64 bigint");
|
|
236
|
+
}
|
|
237
|
+
if ((p.integrator === undefined) !== (p.integratorAllowlist === undefined)) {
|
|
238
|
+
throw new Error("integrator and integratorAllowlist must be supplied together");
|
|
239
|
+
}
|
|
240
|
+
const programId = toBase58(p.programId, "programId");
|
|
241
|
+
const none = () => ({ pubkey: programId, isSigner: false, isWritable: false });
|
|
242
|
+
const keys = [
|
|
243
|
+
key(p.payer, "payer", true, true),
|
|
244
|
+
key(p.owner, "owner", false, false),
|
|
245
|
+
key(p.config, "config", false, true),
|
|
246
|
+
key(p.treasury, "treasury", false, true),
|
|
247
|
+
key(p.namespace, "namespace", false, false),
|
|
248
|
+
key(p.creatorPayout, "creatorPayout", false, true),
|
|
249
|
+
key(p.handle, "handle", false, true),
|
|
250
|
+
key(p.mint, "mint", false, true),
|
|
251
|
+
key(p.ownerTokenAccount, "ownerTokenAccount", false, true),
|
|
252
|
+
key(p.metadata, "metadata", false, true),
|
|
253
|
+
key(p.masterEdition, "masterEdition", false, true),
|
|
254
|
+
key(p.tokenRecord, "tokenRecord", false, true),
|
|
255
|
+
key(p.collectionMint, "collectionMint", false, false),
|
|
256
|
+
key(p.collectionMetadata, "collectionMetadata", false, true),
|
|
257
|
+
key(p.collectionMasterEdition, "collectionMasterEdition", false, false),
|
|
258
|
+
key(p.collectionAuthority, "collectionAuthority", false, false),
|
|
259
|
+
key(TOKEN_METADATA_PROGRAM, "tokenMetadataProgram", false, false),
|
|
260
|
+
key(TOKEN_PROGRAM, "tokenProgram", false, false),
|
|
261
|
+
key(ASSOCIATED_TOKEN_PROGRAM, "associatedTokenProgram", false, false),
|
|
262
|
+
key(SYSTEM_PROGRAM, "systemProgram", false, false),
|
|
263
|
+
key(SYSVAR_INSTRUCTIONS, "sysvarInstructions", false, false),
|
|
264
|
+
p.integrator !== undefined ? key(p.integrator, "integrator", false, true) : none(),
|
|
265
|
+
p.integratorAllowlist !== undefined ? key(p.integratorAllowlist, "integratorAllowlist", false, false) : none(),
|
|
266
|
+
p.minterAuthorization !== undefined ? key(p.minterAuthorization, "minterAuthorization", false, false) : none(),
|
|
267
|
+
key(p.namespacePricing, "namespacePricing", false, false),
|
|
268
|
+
];
|
|
269
|
+
return {
|
|
270
|
+
programId,
|
|
271
|
+
keys,
|
|
272
|
+
data: concat(REGISTER_UNDER_NAMESPACE_V2_DISCRIMINATOR, borshString(p.name), Uint8Array.of(p.handleType), u64le(p.maxPriceLamports)),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/** Read `launch_ts`, `tier_lamports` and `total_handles_sold` from raw Config bytes. */
|
|
276
|
+
export function decodeRootPricingInputs(config) {
|
|
277
|
+
if (config.length < CONFIG_TOTAL_SOLD_OFFSET + 8)
|
|
278
|
+
return null;
|
|
279
|
+
const dv = new DataView(config.buffer, config.byteOffset, config.byteLength);
|
|
280
|
+
const tierLamports = [];
|
|
281
|
+
for (let i = 0; i < NAMESPACE_TIER_COUNT; i++)
|
|
282
|
+
tierLamports.push(dv.getBigUint64(CONFIG_TIERS_OFFSET + 8 * i, true));
|
|
283
|
+
return {
|
|
284
|
+
launchTs: dv.getBigInt64(CONFIG_LAUNCH_TS_OFFSET, true),
|
|
285
|
+
tierLamports,
|
|
286
|
+
totalHandlesSold: dv.getBigUint64(CONFIG_TOTAL_SOLD_OFFSET, true),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* The price `register_under_namespace_v2` charges, from already-read state —
|
|
291
|
+
* `scoped_registration_price` in lib.rs: the creator's band flat when a table
|
|
292
|
+
* exists, else the root band × the ramp.
|
|
293
|
+
*/
|
|
294
|
+
export function scopedRegistrationPrice(pricing, root, nameLen, nowSecs) {
|
|
295
|
+
const i = scopedTierIndex(nameLen);
|
|
296
|
+
if (pricing)
|
|
297
|
+
return pricing.tierLamports[i];
|
|
298
|
+
return rootPriceNow(root.tierLamports[i], root.launchTs, nowSecs, root.totalHandlesSold);
|
|
299
|
+
}
|
|
300
|
+
async function readAccount(rpc, address) {
|
|
301
|
+
const res = (await rpc("getAccountInfo", [address, { encoding: "base64", commitment: "confirmed" }]));
|
|
302
|
+
const v = res?.value;
|
|
303
|
+
if (!v)
|
|
304
|
+
return null;
|
|
305
|
+
const bin = atob(v.data[0]);
|
|
306
|
+
const data = new Uint8Array(bin.length);
|
|
307
|
+
for (let i = 0; i < bin.length; i++)
|
|
308
|
+
data[i] = bin.charCodeAt(i);
|
|
309
|
+
return { owner: v.owner, data };
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Quote `name.label` exactly like the program prices it (pricing-or-fallback).
|
|
313
|
+
* Throws when Config is missing, or when the pricing account exists but is not
|
|
314
|
+
* a valid `NamespacePricing` owned by the program (the program would refuse
|
|
315
|
+
* too), so a caller never shows a price the chain will not honour.
|
|
316
|
+
*/
|
|
317
|
+
export async function quoteScopedName(label, name, opts) {
|
|
318
|
+
const programId = toBase58(opts.programId, "programId");
|
|
319
|
+
const [config, pricingAccount] = await Promise.all([
|
|
320
|
+
opts.find([TE.encode("config")], programId),
|
|
321
|
+
deriveNamespacePricingPda(label, programId, opts.find),
|
|
322
|
+
]);
|
|
323
|
+
const [configAcct, pricingAcct] = await Promise.all([readAccount(opts.rpc, config), readAccount(opts.rpc, pricingAccount)]);
|
|
324
|
+
if (!configAcct)
|
|
325
|
+
throw new Error("registry Config not found");
|
|
326
|
+
const root = decodeRootPricingInputs(configAcct.data);
|
|
327
|
+
if (!root)
|
|
328
|
+
throw new Error("registry Config is too short to read pricing");
|
|
329
|
+
let pricing = null;
|
|
330
|
+
if (pricingAcct && pricingAcct.owner === programId) {
|
|
331
|
+
pricing = decodeNamespacePricing(pricingAcct.data);
|
|
332
|
+
if (!pricing)
|
|
333
|
+
throw new Error(`.${label} pricing account is not a valid NamespacePricing`);
|
|
334
|
+
}
|
|
335
|
+
const nowSecs = opts.nowSecs ?? BigInt(Math.floor(Date.now() / 1000));
|
|
336
|
+
const nameLen = TE.encode(name).length;
|
|
337
|
+
return {
|
|
338
|
+
lamports: scopedRegistrationPrice(pricing, root, nameLen, nowSecs),
|
|
339
|
+
source: pricing ? "custom" : "fallback",
|
|
340
|
+
tierIndex: scopedTierIndex(nameLen),
|
|
341
|
+
pricingAccount,
|
|
342
|
+
pricing,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// upgrade probe
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
/**
|
|
349
|
+
* The instruction to SIMULATE (never send) to learn whether the deployed
|
|
350
|
+
* program has this feature: `set_namespace_pricing` with no accounts. An
|
|
351
|
+
* upgraded program recognizes the discriminator and fails account validation
|
|
352
|
+
* (e.g. 3005 `AccountNotEnoughKeys`); a pre-upgrade program fails with
|
|
353
|
+
* Anchor's `InstructionFallbackNotFound` (101). Interpret the simulation's
|
|
354
|
+
* `err` with {@link interpretNamespacePricingProbe}.
|
|
355
|
+
*/
|
|
356
|
+
export function buildNamespacePricingProbeIx(programId) {
|
|
357
|
+
return { programId: toBase58(programId, "programId"), keys: [], data: SET_NAMESPACE_PRICING_DISCRIMINATOR.slice() };
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Read a `simulateTransaction` result's `value.err` for the probe tx (whose
|
|
361
|
+
* probe instruction is at `probeIndex`). `true` = the program knows the
|
|
362
|
+
* instruction; `false` = it does not, or the result is not the shape we
|
|
363
|
+
* expect (fail closed: hide the feature rather than show a broken button).
|
|
364
|
+
*/
|
|
365
|
+
export function interpretNamespacePricingProbe(err, probeIndex) {
|
|
366
|
+
if (!err || typeof err !== "object")
|
|
367
|
+
return false;
|
|
368
|
+
const ie = err.InstructionError;
|
|
369
|
+
if (!Array.isArray(ie) || ie.length !== 2 || ie[0] !== probeIndex)
|
|
370
|
+
return false;
|
|
371
|
+
const detail = ie[1];
|
|
372
|
+
// Anchor reports both outcomes as `Custom` codes; anything else is an
|
|
373
|
+
// unexpected shape and reads as "not supported".
|
|
374
|
+
if (typeof detail === "object" && detail !== null && typeof detail.Custom === "number") {
|
|
375
|
+
return detail.Custom !== ANCHOR_INSTRUCTION_FALLBACK_NOT_FOUND;
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
package/dist/sha256.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal synchronous SHA-256 (FIPS 180-4). This SDK is zero-dependency and
|
|
3
|
+
* cannot assume WebCrypto is present AND synchronous (`crypto.subtle.digest`
|
|
4
|
+
* is async), so the x402 request-reference derivation (x402.ts) uses this
|
|
5
|
+
* pure implementation. Cross-checked against node:crypto in sdk/test/x402.test.js.
|
|
6
|
+
*/
|
|
7
|
+
/** SHA-256 of `data` (bytes, or a string hashed as its UTF-8 encoding). */
|
|
8
|
+
export declare function sha256(data: Uint8Array | string): Uint8Array;
|
|
9
|
+
/** Lowercase hex of `bytes`. */
|
|
10
|
+
export declare function toHex(bytes: Uint8Array): string;
|
package/dist/sha256.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal synchronous SHA-256 (FIPS 180-4). This SDK is zero-dependency and
|
|
3
|
+
* cannot assume WebCrypto is present AND synchronous (`crypto.subtle.digest`
|
|
4
|
+
* is async), so the x402 request-reference derivation (x402.ts) uses this
|
|
5
|
+
* pure implementation. Cross-checked against node:crypto in sdk/test/x402.test.js.
|
|
6
|
+
*/
|
|
7
|
+
const K = new Uint32Array([
|
|
8
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
9
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
10
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
11
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
12
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
13
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
14
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
15
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
16
|
+
]);
|
|
17
|
+
/** SHA-256 of `data` (bytes, or a string hashed as its UTF-8 encoding). */
|
|
18
|
+
export function sha256(data) {
|
|
19
|
+
const msg = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
20
|
+
const bitLen = BigInt(msg.length) * 8n;
|
|
21
|
+
const padded = new Uint8Array(Math.ceil((msg.length + 9) / 64) * 64);
|
|
22
|
+
padded.set(msg);
|
|
23
|
+
padded[msg.length] = 0x80;
|
|
24
|
+
const dv = new DataView(padded.buffer);
|
|
25
|
+
dv.setBigUint64(padded.length - 8, bitLen, false);
|
|
26
|
+
const H = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
|
|
27
|
+
const W = new Uint32Array(64);
|
|
28
|
+
const rotr = (x, n) => (x >>> n) | (x << (32 - n));
|
|
29
|
+
for (let off = 0; off < padded.length; off += 64) {
|
|
30
|
+
for (let t = 0; t < 16; t++)
|
|
31
|
+
W[t] = dv.getUint32(off + t * 4, false);
|
|
32
|
+
for (let t = 16; t < 64; t++) {
|
|
33
|
+
const w15 = W[t - 15];
|
|
34
|
+
const w2 = W[t - 2];
|
|
35
|
+
const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ (w15 >>> 3);
|
|
36
|
+
const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ (w2 >>> 10);
|
|
37
|
+
W[t] = (W[t - 16] + s0 + W[t - 7] + s1) >>> 0;
|
|
38
|
+
}
|
|
39
|
+
let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
|
|
40
|
+
for (let t = 0; t < 64; t++) {
|
|
41
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
42
|
+
const ch = (e & f) ^ (~e & g);
|
|
43
|
+
const t1 = (h + S1 + ch + K[t] + W[t]) >>> 0;
|
|
44
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
45
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
46
|
+
const t2 = (S0 + maj) >>> 0;
|
|
47
|
+
h = g;
|
|
48
|
+
g = f;
|
|
49
|
+
f = e;
|
|
50
|
+
e = (d + t1) >>> 0;
|
|
51
|
+
d = c;
|
|
52
|
+
c = b;
|
|
53
|
+
b = a;
|
|
54
|
+
a = (t1 + t2) >>> 0;
|
|
55
|
+
}
|
|
56
|
+
H[0] = (H[0] + a) >>> 0;
|
|
57
|
+
H[1] = (H[1] + b) >>> 0;
|
|
58
|
+
H[2] = (H[2] + c) >>> 0;
|
|
59
|
+
H[3] = (H[3] + d) >>> 0;
|
|
60
|
+
H[4] = (H[4] + e) >>> 0;
|
|
61
|
+
H[5] = (H[5] + f) >>> 0;
|
|
62
|
+
H[6] = (H[6] + g) >>> 0;
|
|
63
|
+
H[7] = (H[7] + h) >>> 0;
|
|
64
|
+
}
|
|
65
|
+
const out = new Uint8Array(32);
|
|
66
|
+
const odv = new DataView(out.buffer);
|
|
67
|
+
for (let i = 0; i < 8; i++)
|
|
68
|
+
odv.setUint32(i * 4, H[i], false);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** Lowercase hex of `bytes`. */
|
|
72
|
+
export function toHex(bytes) {
|
|
73
|
+
let s = "";
|
|
74
|
+
for (const b of bytes)
|
|
75
|
+
s += b.toString(16).padStart(2, "0");
|
|
76
|
+
return s;
|
|
77
|
+
}
|
package/dist/voucher.d.ts
CHANGED
|
@@ -25,18 +25,26 @@
|
|
|
25
25
|
* a bound recipient (audit S6 — open/recipient-less vouchers were removed as
|
|
26
26
|
* front-runnable; `claimSecretHash` is vestigial and rejected). An optional integrator referrer (the same trailing accounts as
|
|
27
27
|
* `register`, from `integrator.ts`) is snapshotted so the claim can split
|
|
28
|
-
* the escrow. `expiresAt` must be strictly in the future
|
|
28
|
+
* the escrow. `expiresAt` must be strictly in the future and at most
|
|
29
|
+
* `MAX_VOUCHER_DURATION_SECS` (366 days) away (audit R4-A-2).
|
|
29
30
|
* - `claim_voucher(name)` registers the handle, drawing the fee from the
|
|
30
31
|
* escrow (split integrator/treasury per the snapshot), and closes the
|
|
31
32
|
* voucher — rent back to the original payer. The `claimer` pays only the tx
|
|
32
|
-
* fee and the handle's rent, never the mint fee
|
|
33
|
-
*
|
|
33
|
+
* fee and the handle's rent, never the mint fee. This untokenized claim must
|
|
34
|
+
* be signed by the bound recipient (audit R4-A-3); a relayer uses
|
|
35
|
+
* `claim_voucher_tokenized`. Supply the integrator wallet iff the voucher
|
|
36
|
+
* carries one.
|
|
34
37
|
* - `refund_voucher(name)` returns the whole escrow (+ rent) to the payer,
|
|
35
|
-
* payer-signed,
|
|
38
|
+
* payer-signed, at/after `expiresAt`, or earlier once the name has been
|
|
39
|
+
* registered by someone else (pass `handle`, audit R4-A-2).
|
|
36
40
|
*/
|
|
37
41
|
import type { AddressLike, BuiltInstruction, InstructionKey } from "./delegate.js";
|
|
38
42
|
/** Seed prefix of the voucher PDA: `["voucher", name]`. */
|
|
39
43
|
export declare const VOUCHER_SEED = "voucher";
|
|
44
|
+
/** Longest a voucher may stay open: `create_voucher` refuses an `expiresAt`
|
|
45
|
+
* more than this many seconds ahead (`VoucherExpiryTooFar`, 6112; audit
|
|
46
|
+
* R4-A-2). Mirrors lib.rs `MAX_VOUCHER_DURATION_SECS`. */
|
|
47
|
+
export declare const MAX_VOUCHER_DURATION_SECS: number;
|
|
40
48
|
/** Anchor instruction discriminator `sha256("global:create_voucher")[0..8]`. */
|
|
41
49
|
export declare const CREATE_VOUCHER_DISCRIMINATOR: Uint8Array;
|
|
42
50
|
/** Anchor instruction discriminator `sha256("global:claim_voucher")[0..8]`. */
|
|
@@ -130,8 +138,11 @@ export interface CreateVoucherParams {
|
|
|
130
138
|
export declare function buildCreateVoucherIx(p: CreateVoucherParams): BuiltInstruction;
|
|
131
139
|
export interface ClaimVoucherParams {
|
|
132
140
|
readonly programId: AddressLike;
|
|
133
|
-
/** Pays the tx fee + the handle's rent (NOT the mint fee). Signer.
|
|
134
|
-
*
|
|
141
|
+
/** Pays the tx fee + the handle's rent (NOT the mint fee). Signer. For a
|
|
142
|
+
* bound voucher this MUST be the recipient (audit R4-A-3,
|
|
143
|
+
* `VoucherClaimNotRecipient` 6109): this untokenized claim skips the
|
|
144
|
+
* bundled pNFT, so only the recipient may opt out of it. Relayers use
|
|
145
|
+
* `buildClaimVoucherTokenizedIx`, which stays permissionless. */
|
|
135
146
|
readonly claimer: AddressLike;
|
|
136
147
|
readonly config: AddressLike;
|
|
137
148
|
/** `Config.treasury`. */
|
|
@@ -160,8 +171,15 @@ export interface RefundVoucherParams {
|
|
|
160
171
|
readonly payer: AddressLike;
|
|
161
172
|
readonly voucher: AddressLike;
|
|
162
173
|
readonly name: string;
|
|
174
|
+
/** The `["handle", name]` PDA (audit R4-A-2), appended as the trailing
|
|
175
|
+
* optional account. When that handle is already registered (someone else
|
|
176
|
+
* took the name, so the claim can never succeed) the payer may refund
|
|
177
|
+
* BEFORE `expiresAt`. Omit to send the original two-account form. */
|
|
178
|
+
readonly handle?: AddressLike;
|
|
163
179
|
}
|
|
164
|
-
/** Build `refund_voucher` — payer-signed
|
|
180
|
+
/** Build `refund_voucher` — payer-signed. Valid at/after `expiresAt`, or
|
|
181
|
+
* earlier when `handle` is passed and that name is already registered
|
|
182
|
+
* (audit R4-A-2). */
|
|
165
183
|
export declare function buildRefundVoucherIx(p: RefundVoucherParams): BuiltInstruction;
|
|
166
184
|
/** `sha256("global:claim_voucher_tokenized")[0..8]`. */
|
|
167
185
|
export declare const CLAIM_VOUCHER_TOKENIZED_DISCRIMINATOR: Uint8Array;
|
package/dist/voucher.js
CHANGED
|
@@ -25,18 +25,26 @@
|
|
|
25
25
|
* a bound recipient (audit S6 — open/recipient-less vouchers were removed as
|
|
26
26
|
* front-runnable; `claimSecretHash` is vestigial and rejected). An optional integrator referrer (the same trailing accounts as
|
|
27
27
|
* `register`, from `integrator.ts`) is snapshotted so the claim can split
|
|
28
|
-
* the escrow. `expiresAt` must be strictly in the future
|
|
28
|
+
* the escrow. `expiresAt` must be strictly in the future and at most
|
|
29
|
+
* `MAX_VOUCHER_DURATION_SECS` (366 days) away (audit R4-A-2).
|
|
29
30
|
* - `claim_voucher(name)` registers the handle, drawing the fee from the
|
|
30
31
|
* escrow (split integrator/treasury per the snapshot), and closes the
|
|
31
32
|
* voucher — rent back to the original payer. The `claimer` pays only the tx
|
|
32
|
-
* fee and the handle's rent, never the mint fee
|
|
33
|
-
*
|
|
33
|
+
* fee and the handle's rent, never the mint fee. This untokenized claim must
|
|
34
|
+
* be signed by the bound recipient (audit R4-A-3); a relayer uses
|
|
35
|
+
* `claim_voucher_tokenized`. Supply the integrator wallet iff the voucher
|
|
36
|
+
* carries one.
|
|
34
37
|
* - `refund_voucher(name)` returns the whole escrow (+ rent) to the payer,
|
|
35
|
-
* payer-signed,
|
|
38
|
+
* payer-signed, at/after `expiresAt`, or earlier once the name has been
|
|
39
|
+
* registered by someone else (pass `handle`, audit R4-A-2).
|
|
36
40
|
*/
|
|
37
41
|
import { encodeBase58, decodeBase58_32 } from "./base58.js";
|
|
38
42
|
/** Seed prefix of the voucher PDA: `["voucher", name]`. */
|
|
39
43
|
export const VOUCHER_SEED = "voucher";
|
|
44
|
+
/** Longest a voucher may stay open: `create_voucher` refuses an `expiresAt`
|
|
45
|
+
* more than this many seconds ahead (`VoucherExpiryTooFar`, 6112; audit
|
|
46
|
+
* R4-A-2). Mirrors lib.rs `MAX_VOUCHER_DURATION_SECS`. */
|
|
47
|
+
export const MAX_VOUCHER_DURATION_SECS = 366 * 24 * 60 * 60;
|
|
40
48
|
/** Anchor instruction discriminator `sha256("global:create_voucher")[0..8]`. */
|
|
41
49
|
export const CREATE_VOUCHER_DISCRIMINATOR = Uint8Array.from([22, 97, 32, 21, 104, 137, 188, 143]);
|
|
42
50
|
/** Anchor instruction discriminator `sha256("global:claim_voucher")[0..8]`. */
|
|
@@ -231,20 +239,22 @@ export function buildClaimVoucherIx(p) {
|
|
|
231
239
|
}
|
|
232
240
|
return { programId: toBase58(p.programId, "programId"), keys, data };
|
|
233
241
|
}
|
|
234
|
-
/** Build `refund_voucher` — payer-signed
|
|
242
|
+
/** Build `refund_voucher` — payer-signed. Valid at/after `expiresAt`, or
|
|
243
|
+
* earlier when `handle` is passed and that name is already registered
|
|
244
|
+
* (audit R4-A-2). */
|
|
235
245
|
export function buildRefundVoucherIx(p) {
|
|
236
246
|
const name = encString(p.name);
|
|
237
247
|
const data = new Uint8Array(8 + name.length);
|
|
238
248
|
data.set(REFUND_VOUCHER_DISCRIMINATOR, 0);
|
|
239
249
|
data.set(name, 8);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
250
|
+
const keys = [
|
|
251
|
+
{ pubkey: toBase58(p.payer, "payer"), isSigner: true, isWritable: true },
|
|
252
|
+
{ pubkey: toBase58(p.voucher, "voucher"), isSigner: false, isWritable: true },
|
|
253
|
+
];
|
|
254
|
+
if (p.handle !== undefined) {
|
|
255
|
+
keys.push({ pubkey: toBase58(p.handle, "handle"), isSigner: false, isWritable: false });
|
|
256
|
+
}
|
|
257
|
+
return { programId: toBase58(p.programId, "programId"), keys, data };
|
|
248
258
|
}
|
|
249
259
|
// ===================== claim_voucher_tokenized (gift NFTs, #8335) =====================
|
|
250
260
|
/** `sha256("global:claim_voucher_tokenized")[0..8]`. */
|