@monadns/sdk 2.0.0 → 2.2.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 +266 -18
- package/dist/index.cjs +226 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -12
- package/dist/index.d.ts +133 -12
- package/dist/index.js +216 -28
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +278 -20
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +88 -1
- package/dist/react.d.ts +88 -1
- package/dist/react.js +276 -21
- package/dist/react.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/resolve.ts
|
|
2
|
-
import { namehash } from "viem";
|
|
2
|
+
import { namehash, keccak256, toBytes } from "viem";
|
|
3
3
|
|
|
4
4
|
// src/client.ts
|
|
5
5
|
import {
|
|
@@ -29,7 +29,10 @@ function getMNSClient(config) {
|
|
|
29
29
|
var MNS_REGISTRY = "0x13f963486e741c8d3fcdc0a34a910920339a19ce";
|
|
30
30
|
var MNS_PUBLIC_RESOLVER = "0xa2eb94c88e55d944aced2066c5cec9b759801f97";
|
|
31
31
|
var MNS_CONTROLLER = "0x98866c55adbc73ec6c272bb3604ddbdee3f282a8";
|
|
32
|
+
var MNS_BASE_REGISTRAR = "0x104a49db9318c284d462841b6940bdb46624ca55";
|
|
32
33
|
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
34
|
+
var MAX_AVATAR_BYTES = 51200;
|
|
35
|
+
var DEFAULT_AVATAR_PLACEHOLDER = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg"%3E%3C/svg%3E';
|
|
33
36
|
var registryAbi = [
|
|
34
37
|
{
|
|
35
38
|
name: "resolver",
|
|
@@ -37,6 +40,13 @@ var registryAbi = [
|
|
|
37
40
|
inputs: [{ name: "node", type: "bytes32" }],
|
|
38
41
|
outputs: [{ name: "", type: "address" }],
|
|
39
42
|
stateMutability: "view"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "owner",
|
|
46
|
+
type: "function",
|
|
47
|
+
inputs: [{ name: "node", type: "bytes32" }],
|
|
48
|
+
outputs: [{ name: "", type: "address" }],
|
|
49
|
+
stateMutability: "view"
|
|
40
50
|
}
|
|
41
51
|
];
|
|
42
52
|
var resolverAbi = [
|
|
@@ -161,6 +171,64 @@ async function getTextRecord(name, key, config) {
|
|
|
161
171
|
return null;
|
|
162
172
|
}
|
|
163
173
|
}
|
|
174
|
+
async function getOwner(name, config) {
|
|
175
|
+
try {
|
|
176
|
+
const client = getMNSClient(config);
|
|
177
|
+
const node = namehash(name.toLowerCase());
|
|
178
|
+
const owner = await client.readContract({
|
|
179
|
+
address: MNS_REGISTRY,
|
|
180
|
+
abi: registryAbi,
|
|
181
|
+
functionName: "owner",
|
|
182
|
+
args: [node]
|
|
183
|
+
});
|
|
184
|
+
return owner === ZERO_ADDRESS ? null : owner;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function getResolver(name, config) {
|
|
190
|
+
try {
|
|
191
|
+
const client = getMNSClient(config);
|
|
192
|
+
const node = namehash(name.toLowerCase());
|
|
193
|
+
const resolver = await client.readContract({
|
|
194
|
+
address: MNS_REGISTRY,
|
|
195
|
+
abi: registryAbi,
|
|
196
|
+
functionName: "resolver",
|
|
197
|
+
args: [node]
|
|
198
|
+
});
|
|
199
|
+
return resolver === ZERO_ADDRESS ? null : resolver;
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function getExpiry(name, config) {
|
|
205
|
+
try {
|
|
206
|
+
const client = getMNSClient(config);
|
|
207
|
+
const label = name.replace(".mon", "");
|
|
208
|
+
const labelHash = keccak256(toBytes(label));
|
|
209
|
+
const expiry = await client.readContract({
|
|
210
|
+
address: MNS_BASE_REGISTRAR,
|
|
211
|
+
abi: [
|
|
212
|
+
{
|
|
213
|
+
name: "nameExpires",
|
|
214
|
+
type: "function",
|
|
215
|
+
inputs: [{ name: "id", type: "uint256" }],
|
|
216
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
217
|
+
stateMutability: "view"
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
functionName: "nameExpires",
|
|
221
|
+
args: [BigInt(labelHash)]
|
|
222
|
+
});
|
|
223
|
+
return Number(expiry);
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function getAvailable(name, config) {
|
|
229
|
+
const addr = await resolveName(name, config);
|
|
230
|
+
return addr === null;
|
|
231
|
+
}
|
|
164
232
|
async function getDisplayName(address, config) {
|
|
165
233
|
const name = await lookupAddress(address, config);
|
|
166
234
|
if (name) return name;
|
|
@@ -173,25 +241,28 @@ async function resolveInput(input, config) {
|
|
|
173
241
|
if (trimmed.endsWith(".mon")) return resolveName(trimmed, config);
|
|
174
242
|
return resolveName(`${trimmed}.mon`, config);
|
|
175
243
|
}
|
|
176
|
-
async function
|
|
177
|
-
const addr = await resolveName(name, config);
|
|
178
|
-
return addr !== null;
|
|
179
|
-
}
|
|
180
|
-
async function getAvatarUrl(name, config) {
|
|
244
|
+
async function getAvatarUrl(name, config, options) {
|
|
181
245
|
const raw = await getTextRecord(name, "avatar", config);
|
|
182
|
-
if (!raw) return null;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
/^
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
246
|
+
if (!raw) return options?.fallback || null;
|
|
247
|
+
try {
|
|
248
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
249
|
+
return raw;
|
|
250
|
+
}
|
|
251
|
+
if (raw.startsWith("ipfs://")) {
|
|
252
|
+
const hash = raw.slice(7);
|
|
253
|
+
return `https://ipfs.io/ipfs/${hash}`;
|
|
254
|
+
}
|
|
255
|
+
if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(raw)) {
|
|
256
|
+
return `https://ipfs.io/ipfs/${raw}`;
|
|
257
|
+
}
|
|
258
|
+
if (raw.startsWith("ar://")) {
|
|
259
|
+
const hash = raw.slice(5);
|
|
260
|
+
return `https://arweave.net/${hash}`;
|
|
261
|
+
}
|
|
262
|
+
const nftMatch = raw.match(
|
|
263
|
+
/^eip155:(\d+)\/(erc721|erc1155):0x([a-fA-F0-9]{40})\/(\d+)$/
|
|
264
|
+
);
|
|
265
|
+
if (nftMatch) {
|
|
195
266
|
const client = getMNSClient(config);
|
|
196
267
|
const contract = `0x${nftMatch[3]}`;
|
|
197
268
|
const tokenId = BigInt(nftMatch[4]);
|
|
@@ -219,15 +290,16 @@ async function getAvatarUrl(name, config) {
|
|
|
219
290
|
if (image && image.startsWith("ipfs://")) {
|
|
220
291
|
image = `https://ipfs.io/ipfs/${image.slice(7)}`;
|
|
221
292
|
}
|
|
222
|
-
return image;
|
|
223
|
-
} catch {
|
|
224
|
-
return null;
|
|
293
|
+
return image || options?.fallback || null;
|
|
225
294
|
}
|
|
295
|
+
if (raw.startsWith("data:")) {
|
|
296
|
+
return raw;
|
|
297
|
+
}
|
|
298
|
+
return options?.fallback || null;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.warn("Failed to resolve avatar:", error);
|
|
301
|
+
return options?.fallback || null;
|
|
226
302
|
}
|
|
227
|
-
if (raw.startsWith("data:")) {
|
|
228
|
-
return raw;
|
|
229
|
-
}
|
|
230
|
-
return null;
|
|
231
303
|
}
|
|
232
304
|
function clearCache() {
|
|
233
305
|
nameCache.clear();
|
|
@@ -236,6 +308,102 @@ function clearCache() {
|
|
|
236
308
|
|
|
237
309
|
// src/write.ts
|
|
238
310
|
import { namehash as namehash2 } from "viem";
|
|
311
|
+
|
|
312
|
+
// src/utils.ts
|
|
313
|
+
function validateAvatarUri(uri) {
|
|
314
|
+
if (!uri) {
|
|
315
|
+
return { valid: false, error: "Avatar URI is required" };
|
|
316
|
+
}
|
|
317
|
+
if (uri.startsWith("data:")) {
|
|
318
|
+
const sizeBytes = new Blob([uri]).size;
|
|
319
|
+
if (sizeBytes > MAX_AVATAR_BYTES) {
|
|
320
|
+
return {
|
|
321
|
+
valid: false,
|
|
322
|
+
error: `Avatar size (${(sizeBytes / 1024).toFixed(1)}KB) exceeds ${MAX_AVATAR_BYTES / 1024}KB limit. Please optimize as WebP or SVG for better performance.`,
|
|
323
|
+
sizeBytes
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return { valid: true, sizeBytes };
|
|
327
|
+
}
|
|
328
|
+
const validSchemes = ["http://", "https://", "ipfs://", "eip155:", "ar://"];
|
|
329
|
+
const hasValidScheme = validSchemes.some((scheme) => uri.startsWith(scheme));
|
|
330
|
+
if (!hasValidScheme) {
|
|
331
|
+
if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(uri)) {
|
|
332
|
+
return { valid: true };
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
valid: false,
|
|
336
|
+
error: "Avatar must be a valid HTTP, IPFS, Arweave, or NFT URI"
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return { valid: true };
|
|
340
|
+
}
|
|
341
|
+
async function validateAvatarFull(uri, options) {
|
|
342
|
+
const maxBytes = options?.maxBytes || MAX_AVATAR_BYTES;
|
|
343
|
+
const timeout = options?.timeout || 5e3;
|
|
344
|
+
const formatValidation = validateAvatarUri(uri);
|
|
345
|
+
if (!formatValidation.valid) {
|
|
346
|
+
throw new Error(formatValidation.error);
|
|
347
|
+
}
|
|
348
|
+
if (uri.startsWith("data:")) {
|
|
349
|
+
return { valid: true, sizeBytes: formatValidation.sizeBytes };
|
|
350
|
+
}
|
|
351
|
+
if (uri.startsWith("eip155:")) {
|
|
352
|
+
return { valid: true };
|
|
353
|
+
}
|
|
354
|
+
let checkUrl = uri;
|
|
355
|
+
if (uri.startsWith("ipfs://")) {
|
|
356
|
+
checkUrl = `https://ipfs.io/ipfs/${uri.slice(7)}`;
|
|
357
|
+
} else if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(uri)) {
|
|
358
|
+
checkUrl = `https://ipfs.io/ipfs/${uri}`;
|
|
359
|
+
} else if (uri.startsWith("ar://")) {
|
|
360
|
+
checkUrl = `https://arweave.net/${uri.slice(5)}`;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const controller = new AbortController();
|
|
364
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
365
|
+
const response = await fetch(checkUrl, {
|
|
366
|
+
method: "HEAD",
|
|
367
|
+
signal: controller.signal
|
|
368
|
+
});
|
|
369
|
+
clearTimeout(timeoutId);
|
|
370
|
+
const contentLength = response.headers.get("content-length");
|
|
371
|
+
if (contentLength) {
|
|
372
|
+
const sizeBytes = parseInt(contentLength, 10);
|
|
373
|
+
if (sizeBytes > maxBytes) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`Avatar file size (${(sizeBytes / 1024).toFixed(1)}KB) exceeds ${maxBytes / 1024}KB limit. Please optimize as WebP or SVG for better performance.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return { valid: true, sizeBytes };
|
|
379
|
+
}
|
|
380
|
+
console.warn(
|
|
381
|
+
`Could not determine avatar size for ${uri}. Ensure it's under ${maxBytes / 1024}KB.`
|
|
382
|
+
);
|
|
383
|
+
return { valid: true };
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (error.name === "AbortError") {
|
|
386
|
+
console.warn(`Avatar size check timed out for ${uri}`);
|
|
387
|
+
return { valid: true };
|
|
388
|
+
}
|
|
389
|
+
if (error.message.includes("exceeds")) {
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
console.warn(`Could not check avatar size: ${error.message}`);
|
|
393
|
+
return { valid: true };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async function getRemoteAvatarSize(url) {
|
|
397
|
+
try {
|
|
398
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
399
|
+
const contentLength = response.headers.get("content-length");
|
|
400
|
+
return contentLength ? parseInt(contentLength, 10) : null;
|
|
401
|
+
} catch {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/write.ts
|
|
239
407
|
var controllerReadAbi = [
|
|
240
408
|
{
|
|
241
409
|
name: "available",
|
|
@@ -352,6 +520,17 @@ async function getRegisterTx(label, ownerAddress, durationYears = 1, config) {
|
|
|
352
520
|
};
|
|
353
521
|
}
|
|
354
522
|
function getSetTextTx(name, key, value) {
|
|
523
|
+
if (key === "avatar" && value.startsWith("data:")) {
|
|
524
|
+
const validation = validateAvatarUri(value);
|
|
525
|
+
if (!validation.valid) {
|
|
526
|
+
throw new Error(validation.error);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (key === "avatar" && !value.startsWith("data:")) {
|
|
530
|
+
console.warn(
|
|
531
|
+
"\u26A0\uFE0F Avatar set without size validation. For best practices, use setAvatarValidated() or validateAvatarFull() before calling setAvatar()."
|
|
532
|
+
);
|
|
533
|
+
}
|
|
355
534
|
const node = namehash2(
|
|
356
535
|
name.endsWith(".mon") ? name : `${name}.mon`
|
|
357
536
|
);
|
|
@@ -384,6 +563,9 @@ var TEXT_RECORD_KEYS = {
|
|
|
384
563
|
telegram: "org.telegram"
|
|
385
564
|
};
|
|
386
565
|
export {
|
|
566
|
+
DEFAULT_AVATAR_PLACEHOLDER,
|
|
567
|
+
MAX_AVATAR_BYTES,
|
|
568
|
+
MNS_BASE_REGISTRAR,
|
|
387
569
|
MNS_CONTROLLER,
|
|
388
570
|
MNS_PUBLIC_RESOLVER,
|
|
389
571
|
MNS_REGISTRY,
|
|
@@ -391,18 +573,24 @@ export {
|
|
|
391
573
|
clearCache,
|
|
392
574
|
controllerReadAbi,
|
|
393
575
|
controllerWriteAbi,
|
|
576
|
+
getAvailable,
|
|
394
577
|
getAvatarUrl,
|
|
395
578
|
getDisplayName,
|
|
579
|
+
getExpiry,
|
|
396
580
|
getMNSClient,
|
|
581
|
+
getOwner,
|
|
397
582
|
getRegisterTx,
|
|
398
583
|
getRegistrationInfo,
|
|
584
|
+
getRemoteAvatarSize,
|
|
585
|
+
getResolver,
|
|
399
586
|
getSetAddrTx,
|
|
400
587
|
getSetTextTx,
|
|
401
588
|
getTextRecord,
|
|
402
|
-
isRegistered,
|
|
403
589
|
lookupAddress,
|
|
404
590
|
resolveInput,
|
|
405
591
|
resolveName,
|
|
406
|
-
resolverWriteAbi
|
|
592
|
+
resolverWriteAbi,
|
|
593
|
+
validateAvatarFull,
|
|
594
|
+
validateAvatarUri
|
|
407
595
|
};
|
|
408
596
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/resolve.ts","../src/client.ts","../src/constants.ts","../src/write.ts"],"sourcesContent":["import { namehash } from 'viem';\nimport { getMNSClient, type MNSClientConfig } from './client';\nimport {\n MNS_REGISTRY,\n ZERO_ADDRESS,\n registryAbi,\n resolverAbi,\n} from './constants';\n\n// ─── Cache ─────────────────────────────────────────────────────\n\nconst nameCache = new Map<string, { value: string | null; ts: number }>();\nconst addrCache = new Map<string, { value: string | null; ts: number }>();\n\nconst CACHE_TTL = 60_000; // 1 minute\n\nfunction getCached(\n cache: Map<string, { value: string | null; ts: number }>,\n key: string,\n) {\n const entry = cache.get(key);\n if (entry && Date.now() - entry.ts < CACHE_TTL) return entry.value;\n return undefined;\n}\n\n// ─── Forward Resolution ────────────────────────────────────────\n\n/**\n * Resolve a .mon name to an address.\n *\n * @param name - Full name including .mon (e.g. \"alice.mon\")\n * @param config - Optional client configuration\n * @returns The resolved address, or null if not found\n *\n * @example\n * ```ts\n * import { resolveName } from '@monadns/sdk'\n *\n * const address = await resolveName('alice.mon')\n * // '0xa05a8BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47'\n * ```\n */\nexport async function resolveName(\n name: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const key = name.toLowerCase();\n const cached = getCached(addrCache, key);\n if (cached !== undefined) return cached;\n\n try {\n const client = getMNSClient(config);\n const node = namehash(key) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [node],\n });\n\n if (resolver === ZERO_ADDRESS) {\n addrCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n\n const addr = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'addr',\n args: [node],\n });\n\n const result = addr === ZERO_ADDRESS ? null : addr;\n addrCache.set(key, { value: result, ts: Date.now() });\n return result;\n } catch {\n addrCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n}\n\n// ─── Reverse Resolution ────────────────────────────────────────\n\n/**\n * Look up the primary .mon name for an address.\n *\n * @param address - A 0x-prefixed Monad address\n * @param config - Optional client configuration\n * @returns The primary name (e.g. \"alice.mon\"), or null\n *\n * @example\n * ```ts\n * import { lookupAddress } from '@monadns/sdk'\n *\n * const name = await lookupAddress('0xa05a8BF1...')\n * // 'alice.mon'\n * ```\n */\nexport async function lookupAddress(\n address: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const key = address.toLowerCase();\n const cached = getCached(nameCache, key);\n if (cached !== undefined) return cached;\n\n try {\n const client = getMNSClient(config);\n const reverseNode = namehash(\n `${key.slice(2)}.addr.reverse`,\n ) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [reverseNode],\n });\n\n if (resolver === ZERO_ADDRESS) {\n nameCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n\n const name = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'name',\n args: [reverseNode],\n });\n\n const result = name || null;\n nameCache.set(key, { value: result, ts: Date.now() });\n return result;\n } catch {\n nameCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n}\n\n// ─── Text Records ──────────────────────────────────────────────\n\n/**\n * Get a text record for a .mon name.\n *\n * @param name - Full name including .mon\n * @param key - The text record key (e.g. \"avatar\", \"url\", \"com.twitter\")\n * @param config - Optional client configuration\n *\n * @example\n * ```ts\n * const avatar = await getTextRecord('alice.mon', 'avatar')\n * const twitter = await getTextRecord('alice.mon', 'com.twitter')\n * ```\n */\nexport async function getTextRecord(\n name: string,\n key: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n try {\n const client = getMNSClient(config);\n const node = namehash(name.toLowerCase()) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [node],\n });\n\n if (resolver === ZERO_ADDRESS) return null;\n\n const value = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'text',\n args: [node, key],\n });\n\n return value || null;\n } catch {\n return null;\n }\n}\n\n// ─── Helpers ───────────────────────────────────────────────────\n\n/**\n * Get a display-friendly name for an address.\n * Returns the .mon name if set, otherwise a truncated address.\n *\n * @example\n * ```ts\n * const display = await getDisplayName('0xa05a8BF1...')\n * // 'alice.mon' or '0xa05a...Cc47'\n * ```\n */\nexport async function getDisplayName(\n address: string,\n config?: MNSClientConfig,\n): Promise<string> {\n const name = await lookupAddress(address, config);\n if (name) return name;\n return `${address.slice(0, 6)}...${address.slice(-4)}`;\n}\n\n/**\n * Smart resolver — accepts either a .mon name or a raw address.\n * Useful for input fields where users can type either.\n *\n * @returns The resolved address, or null if invalid/not found\n *\n * @example\n * ```ts\n * const addr = await resolveInput('alice.mon') // resolves name\n * const addr = await resolveInput('alice') // tries alice.mon\n * const addr = await resolveInput('0xa05a...') // passes through\n * ```\n */\nexport async function resolveInput(\n input: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const trimmed = input.trim();\n if (!trimmed) return null;\n\n if (trimmed.startsWith('0x') && trimmed.length === 42) return trimmed;\n if (trimmed.endsWith('.mon')) return resolveName(trimmed, config);\n\n // Try appending .mon\n return resolveName(`${trimmed}.mon`, config);\n}\n\n/**\n * Check if a .mon name is registered.\n *\n * @example\n * ```ts\n * const taken = await isRegistered('alice.mon')\n * ```\n */\nexport async function isRegistered(\n name: string,\n config?: MNSClientConfig,\n): Promise<boolean> {\n const addr = await resolveName(name, config);\n return addr !== null;\n}\n\n/**\n * Get a resolved avatar URL for a .mon name.\n *\n * Handles multiple avatar formats:\n * - Direct HTTPS URLs: returned as-is\n * - IPFS URIs (ipfs://...): resolved via public gateway\n * - NFT references (eip155:chainId/erc721:contract/tokenId): fetches tokenURI and extracts image\n *\n * @param name - Full name including .mon\n * @param config - Optional client configuration\n * @returns A usable HTTPS URL, or null\n *\n * @example\n * ```ts\n * const avatarUrl = await getAvatarUrl('alice.mon')\n * // 'https://example.com/avatar.png'\n * ```\n */\nexport async function getAvatarUrl(\n name: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const raw = await getTextRecord(name, 'avatar', config);\n if (!raw) return null;\n\n // Direct HTTP(S) URL\n if (raw.startsWith('http://') || raw.startsWith('https://')) {\n return raw;\n }\n\n // IPFS\n if (raw.startsWith('ipfs://')) {\n const hash = raw.slice(7);\n return `https://ipfs.io/ipfs/${hash}`;\n }\n\n // NFT reference: eip155:{chainId}/erc721:{contract}/{tokenId}\n // or eip155:{chainId}/erc1155:{contract}/{tokenId}\n const nftMatch = raw.match(\n /^eip155:(\\d+)\\/(erc721|erc1155):0x([a-fA-F0-9]{40})\\/(\\d+)$/,\n );\n if (nftMatch) {\n try {\n const client = getMNSClient(config);\n const contract = `0x${nftMatch[3]}` as `0x${string}`;\n const tokenId = BigInt(nftMatch[4]);\n\n const tokenUriAbi = [\n {\n name: nftMatch[2] === 'erc721' ? 'tokenURI' : 'uri',\n type: 'function',\n inputs: [{ name: 'tokenId', type: 'uint256' }],\n outputs: [{ name: '', type: 'string' }],\n stateMutability: 'view',\n },\n ] as const;\n\n let tokenUri = (await client.readContract({\n address: contract,\n abi: tokenUriAbi,\n functionName: nftMatch[2] === 'erc721' ? 'tokenURI' : 'uri',\n args: [tokenId],\n })) as string;\n\n // Resolve IPFS tokenURI\n if (tokenUri.startsWith('ipfs://')) {\n tokenUri = `https://ipfs.io/ipfs/${tokenUri.slice(7)}`;\n }\n\n // Fetch metadata JSON and extract image\n const res = await fetch(tokenUri);\n const metadata = await res.json();\n let image = metadata.image || metadata.image_url || null;\n\n if (image && image.startsWith('ipfs://')) {\n image = `https://ipfs.io/ipfs/${image.slice(7)}`;\n }\n\n return image;\n } catch {\n return null;\n }\n }\n\n // data: URIs\n if (raw.startsWith('data:')) {\n return raw;\n }\n\n return null;\n}\n\n/**\n * Clear the internal resolution cache.\n * Useful after a new registration.\n */\nexport function clearCache() {\n nameCache.clear();\n addrCache.clear();\n}\n","import {\n createPublicClient,\n http,\n type PublicClient,\n type Chain,\n type Transport,\n} from 'viem';\nimport { monad } from 'viem/chains';\n\nexport type MNSClientConfig = {\n /** Custom RPC URL. Defaults to the public Monad RPC. */\n rpcUrl?: string;\n /** Existing viem PublicClient to reuse instead of creating a new one. */\n client?: PublicClient<Transport, Chain>;\n};\n\nlet defaultClient: PublicClient<Transport, Chain> | null = null;\n\n/**\n * Get or create the MNS public client.\n * Reuses a singleton by default; pass config to override.\n */\nexport function getMNSClient(\n config?: MNSClientConfig,\n): PublicClient<Transport, Chain> {\n if (config?.client) return config.client;\n\n if (config?.rpcUrl) {\n return createPublicClient({\n chain: monad,\n transport: http(config.rpcUrl),\n });\n }\n\n if (!defaultClient) {\n defaultClient = createPublicClient({\n chain: monad,\n transport: http(),\n });\n }\n\n return defaultClient;\n}\n","export const MNS_REGISTRY =\n '0x13f963486e741c8d3fcdc0a34a910920339a19ce' as const;\nexport const MNS_PUBLIC_RESOLVER =\n '0xa2eb94c88e55d944aced2066c5cec9b759801f97' as const;\nexport const MNS_CONTROLLER =\n '0x98866c55adbc73ec6c272bb3604ddbdee3f282a8' as const;\n\nexport const ZERO_ADDRESS =\n '0x0000000000000000000000000000000000000000' as const;\n\nexport const registryAbi = [\n {\n name: 'resolver',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'address' }],\n stateMutability: 'view',\n },\n] as const;\n\nexport const resolverAbi = [\n {\n name: 'addr',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'address' }],\n stateMutability: 'view',\n },\n {\n name: 'name',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'string' }],\n stateMutability: 'view',\n },\n {\n name: 'text',\n type: 'function',\n inputs: [\n { name: 'node', type: 'bytes32' },\n { name: 'key', type: 'string' },\n ],\n outputs: [{ name: '', type: 'string' }],\n stateMutability: 'view',\n },\n] as const;\n","import { namehash } from 'viem';\nimport { getMNSClient, type MNSClientConfig } from './client';\nimport { MNS_CONTROLLER, MNS_PUBLIC_RESOLVER } from './constants';\n\n/**\n * MNS Write Helpers\n *\n * These return transaction parameters ({ address, abi, functionName, args, value })\n * that you pass to wagmi's writeContract or any wallet client.\n * The user signs with their own wallet.\n */\n\n// ─── ABIs ──────────────────────────────────────────────────────\n\nexport const controllerReadAbi = [\n {\n name: 'available',\n type: 'function',\n inputs: [{ name: 'label', type: 'string' }],\n outputs: [{ name: '', type: 'bool' }],\n stateMutability: 'view',\n },\n {\n name: 'rentPrice',\n type: 'function',\n inputs: [\n { name: 'label', type: 'string' },\n { name: 'duration', type: 'uint256' },\n ],\n outputs: [{ name: '', type: 'uint256' }],\n stateMutability: 'view',\n },\n {\n name: 'hasClaimedFreebie',\n type: 'function',\n inputs: [{ name: 'user', type: 'address' }],\n outputs: [{ name: '', type: 'bool' }],\n stateMutability: 'view',\n },\n] as const;\n\nexport const controllerWriteAbi = [\n {\n name: 'register',\n type: 'function',\n inputs: [\n { name: 'label', type: 'string' },\n { name: 'owner', type: 'address' },\n { name: 'duration', type: 'uint256' },\n ],\n outputs: [{ name: '', type: 'uint256' }],\n stateMutability: 'payable',\n },\n] as const;\n\nexport const resolverWriteAbi = [\n {\n name: 'setText',\n type: 'function',\n inputs: [\n { name: 'node', type: 'bytes32' },\n { name: 'key', type: 'string' },\n { name: 'value', type: 'string' },\n ],\n outputs: [],\n stateMutability: 'nonpayable',\n },\n {\n name: 'setAddr',\n type: 'function',\n inputs: [\n { name: 'node', type: 'bytes32' },\n { name: 'a', type: 'address' },\n ],\n outputs: [],\n stateMutability: 'nonpayable',\n },\n] as const;\n\n// ─── Types ─────────────────────────────────────────────────────\n\nexport type RegistrationInfo = {\n available: boolean;\n price: bigint;\n isFreebie: boolean;\n hasClaimed: boolean;\n duration: bigint;\n label: string;\n};\n\n// ─── Registration ──────────────────────────────────────────────\n\nconst ONE_YEAR = BigInt(365 * 24 * 60 * 60);\n\n/**\n * Get all info needed before registration.\n *\n * @example\n * ```ts\n * const info = await getRegistrationInfo('alice', userAddress)\n * // { available: true, price: 96831564n..., isFreebie: true, ... }\n * ```\n */\nexport async function getRegistrationInfo(\n label: string,\n userAddress: string,\n durationYears = 1,\n config?: MNSClientConfig,\n): Promise<RegistrationInfo> {\n const client = getMNSClient(config);\n const duration = ONE_YEAR * BigInt(durationYears);\n\n const [available, price, hasClaimed, balance] = await Promise.all([\n client.readContract({\n address: MNS_CONTROLLER,\n abi: controllerReadAbi,\n functionName: 'available',\n args: [label],\n }),\n client.readContract({\n address: MNS_CONTROLLER,\n abi: controllerReadAbi,\n functionName: 'rentPrice',\n args: [label, duration],\n }),\n client.readContract({\n address: MNS_CONTROLLER,\n abi: controllerReadAbi,\n functionName: 'hasClaimedFreebie',\n args: [userAddress as `0x${string}`],\n }),\n client.getBalance({ address: userAddress as `0x${string}` }),\n ]);\n\n const isFreebie = balance >= 10n * 10n ** 18n && !hasClaimed;\n\n return { available, price, isFreebie, hasClaimed, duration, label };\n}\n\n/**\n * Get transaction parameters for registering a .mon name.\n * Pass the result to wagmi's writeContract.\n *\n * @example\n * ```ts\n * const tx = await getRegisterTx('alice', userAddress)\n * writeContract(tx)\n * ```\n */\nexport async function getRegisterTx(\n label: string,\n ownerAddress: string,\n durationYears = 1,\n config?: MNSClientConfig,\n) {\n const info = await getRegistrationInfo(\n label,\n ownerAddress,\n durationYears,\n config,\n );\n\n if (!info.available) {\n throw new Error(`${label}.mon is not available`);\n }\n\n return {\n address: MNS_CONTROLLER,\n abi: controllerWriteAbi,\n functionName: 'register' as const,\n args: [label, ownerAddress as `0x${string}`, info.duration] as const,\n value: info.isFreebie ? 0n : info.price,\n gas: 3_000_000n,\n _meta: {\n label,\n isFreebie: info.isFreebie,\n price: info.price,\n duration: info.duration,\n },\n };\n}\n\n// ─── Text Records ──────────────────────────────────────────────\n\n/**\n * Get transaction parameters for setting a text record.\n * The connected user must own the name.\n *\n * @example\n * ```ts\n * const tx = getSetTextTx('alice.mon', 'avatar', 'https://example.com/pic.png')\n * writeContract(tx)\n * ```\n */\nexport function getSetTextTx(name: string, key: string, value: string) {\n const node = namehash(\n name.endsWith('.mon') ? name : `${name}.mon`,\n ) as `0x${string}`;\n\n return {\n address: MNS_PUBLIC_RESOLVER,\n abi: resolverWriteAbi,\n functionName: 'setText' as const,\n args: [node, key, value] as const,\n };\n}\n\n/**\n * Get transaction parameters for setting the address record.\n */\nexport function getSetAddrTx(name: string, addr: string) {\n const node = namehash(\n name.endsWith('.mon') ? name : `${name}.mon`,\n ) as `0x${string}`;\n\n return {\n address: MNS_PUBLIC_RESOLVER,\n abi: resolverWriteAbi,\n functionName: 'setAddr' as const,\n args: [node, addr as `0x${string}`] as const,\n };\n}\n\nexport const TEXT_RECORD_KEYS = {\n avatar: 'avatar',\n url: 'url',\n description: 'description',\n email: 'email',\n twitter: 'com.twitter',\n github: 'com.github',\n discord: 'com.discord',\n telegram: 'org.telegram',\n} as const;\n"],"mappings":"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","names":["namehash","namehash"]}
|
|
1
|
+
{"version":3,"sources":["../src/resolve.ts","../src/client.ts","../src/constants.ts","../src/write.ts","../src/utils.ts"],"sourcesContent":["import { namehash, keccak256, toBytes } from 'viem';\nimport { getMNSClient, type MNSClientConfig } from './client';\nimport {\n MNS_REGISTRY,\n MNS_BASE_REGISTRAR,\n ZERO_ADDRESS,\n registryAbi,\n resolverAbi,\n} from './constants';\n\n// ─── Cache ─────────────────────────────────────────────────────\n\nconst nameCache = new Map<string, { value: string | null; ts: number }>();\nconst addrCache = new Map<string, { value: string | null; ts: number }>();\n\nconst CACHE_TTL = 60_000; // 1 minute\n\nfunction getCached(\n cache: Map<string, { value: string | null; ts: number }>,\n key: string,\n) {\n const entry = cache.get(key);\n if (entry && Date.now() - entry.ts < CACHE_TTL) return entry.value;\n return undefined;\n}\n\n// ─── Forward Resolution ────────────────────────────────────────\n\n/**\n * Resolve a .mon name to an address.\n *\n * @param name - Full name including .mon (e.g. \"alice.mon\")\n * @param config - Optional client configuration\n * @returns The resolved address, or null if not found\n *\n * @example\n * ```ts\n * import { resolveName } from '@monadns/sdk'\n *\n * const address = await resolveName('alice.mon')\n * // '0xa05a8BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47'\n * ```\n */\nexport async function resolveName(\n name: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const key = name.toLowerCase();\n const cached = getCached(addrCache, key);\n if (cached !== undefined) return cached;\n\n try {\n const client = getMNSClient(config);\n const node = namehash(key) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [node],\n });\n\n if (resolver === ZERO_ADDRESS) {\n addrCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n\n const addr = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'addr',\n args: [node],\n });\n\n const result = addr === ZERO_ADDRESS ? null : addr;\n addrCache.set(key, { value: result, ts: Date.now() });\n return result;\n } catch {\n addrCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n}\n\n// ─── Reverse Resolution ────────────────────────────────────────\n\n/**\n * Look up the primary .mon name for an address.\n *\n * @param address - A 0x-prefixed Monad address\n * @param config - Optional client configuration\n * @returns The primary name (e.g. \"alice.mon\"), or null\n *\n * @example\n * ```ts\n * import { lookupAddress } from '@monadns/sdk'\n *\n * const name = await lookupAddress('0xa05a8BF1...')\n * // 'alice.mon'\n * ```\n */\nexport async function lookupAddress(\n address: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const key = address.toLowerCase();\n const cached = getCached(nameCache, key);\n if (cached !== undefined) return cached;\n\n try {\n const client = getMNSClient(config);\n const reverseNode = namehash(\n `${key.slice(2)}.addr.reverse`,\n ) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [reverseNode],\n });\n\n if (resolver === ZERO_ADDRESS) {\n nameCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n\n const name = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'name',\n args: [reverseNode],\n });\n\n const result = name || null;\n nameCache.set(key, { value: result, ts: Date.now() });\n return result;\n } catch {\n nameCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n}\n\n// ─── Text Records ──────────────────────────────────────────────\n\n/**\n * Get a text record for a .mon name.\n *\n * @param name - Full name including .mon\n * @param key - The text record key (e.g. \"avatar\", \"url\", \"com.twitter\")\n * @param config - Optional client configuration\n *\n * @example\n * ```ts\n * const avatar = await getTextRecord('alice.mon', 'avatar')\n * const twitter = await getTextRecord('alice.mon', 'com.twitter')\n * ```\n */\nexport async function getTextRecord(\n name: string,\n key: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n try {\n const client = getMNSClient(config);\n const node = namehash(name.toLowerCase()) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [node],\n });\n\n if (resolver === ZERO_ADDRESS) return null;\n\n const value = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'text',\n args: [node, key],\n });\n\n return value || null;\n } catch {\n return null;\n }\n}\n\n// ─── Ownership & Registry Info ─────────────────────────────────\n\n/**\n * Get the owner of a .mon name.\n *\n * @param name - Full name including .mon (e.g. \"alice.mon\")\n * @param config - Optional client configuration\n * @returns The owner address, or null if not registered\n *\n * @example\n * ```ts\n * const owner = await getOwner('alice.mon')\n * // '0xa05a8BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47'\n * ```\n */\nexport async function getOwner(\n name: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n try {\n const client = getMNSClient(config);\n const node = namehash(name.toLowerCase()) as `0x${string}`;\n\n const owner = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'owner',\n args: [node],\n });\n\n return owner === ZERO_ADDRESS ? null : owner;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the resolver address for a .mon name.\n *\n * @param name - Full name including .mon\n * @param config - Optional client configuration\n * @returns The resolver contract address, or null if not set\n *\n * @example\n * ```ts\n * const resolver = await getResolver('alice.mon')\n * // '0xa2eb94c88e55d944aced2066c5cec9b759801f97'\n * ```\n */\nexport async function getResolver(\n name: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n try {\n const client = getMNSClient(config);\n const node = namehash(name.toLowerCase()) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [node],\n });\n\n return resolver === ZERO_ADDRESS ? null : resolver;\n } catch {\n return null;\n }\n}\n\n/**\n * Get expiry timestamp for a .mon name.\n *\n * @param name - Full name including .mon\n * @param config - Optional client configuration\n * @returns Unix timestamp (seconds) when the name expires, or null if not found\n *\n * @example\n * ```ts\n * const expiry = await getExpiry('alice.mon')\n * // 1735689600 (Unix timestamp)\n *\n * const expiryDate = new Date(expiry * 1000)\n * const daysLeft = Math.floor((expiry * 1000 - Date.now()) / (1000 * 60 * 60 * 24))\n * ```\n */\nexport async function getExpiry(\n name: string,\n config?: MNSClientConfig,\n): Promise<number | null> {\n try {\n const client = getMNSClient(config);\n const label = name.replace('.mon', '');\n const labelHash = keccak256(toBytes(label));\n\n const expiry = await client.readContract({\n address: MNS_BASE_REGISTRAR,\n abi: [\n {\n name: 'nameExpires',\n type: 'function',\n inputs: [{ name: 'id', type: 'uint256' }],\n outputs: [{ name: '', type: 'uint256' }],\n stateMutability: 'view',\n },\n ] as const,\n functionName: 'nameExpires',\n args: [BigInt(labelHash)],\n });\n\n return Number(expiry);\n } catch {\n return null;\n }\n}\n\n/**\n * Check if a .mon name is available for registration.\n *\n * @param name - Full name including .mon\n * @param config - Optional client configuration\n * @returns true if available (not registered), false if taken\n *\n * @example\n * ```ts\n * const available = await getAvailable('alice.mon')\n * if (available) {\n * console.log('alice.mon is available!')\n * }\n * ```\n */\nexport async function getAvailable(\n name: string,\n config?: MNSClientConfig,\n): Promise<boolean> {\n const addr = await resolveName(name, config);\n return addr === null;\n}\n\n// ─── Helpers ───────────────────────────────────────────────────\n\n/**\n * Get a display-friendly name for an address.\n * Returns the .mon name if set, otherwise a truncated address.\n *\n * @example\n * ```ts\n * const display = await getDisplayName('0xa05a8BF1...')\n * // 'alice.mon' or '0xa05a...Cc47'\n * ```\n */\nexport async function getDisplayName(\n address: string,\n config?: MNSClientConfig,\n): Promise<string> {\n const name = await lookupAddress(address, config);\n if (name) return name;\n return `${address.slice(0, 6)}...${address.slice(-4)}`;\n}\n\n/**\n * Smart resolver — accepts either a .mon name or a raw address.\n * Useful for input fields where users can type either.\n *\n * @returns The resolved address, or null if invalid/not found\n *\n * @example\n * ```ts\n * const addr = await resolveInput('alice.mon') // resolves name\n * const addr = await resolveInput('alice') // tries alice.mon\n * const addr = await resolveInput('0xa05a...') // passes through\n * ```\n */\nexport async function resolveInput(\n input: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const trimmed = input.trim();\n if (!trimmed) return null;\n\n if (trimmed.startsWith('0x') && trimmed.length === 42) return trimmed;\n if (trimmed.endsWith('.mon')) return resolveName(trimmed, config);\n\n // Try appending .mon\n return resolveName(`${trimmed}.mon`, config);\n}\n\n/**\n * Get a resolved avatar URL for a .mon name.\n *\n * Handles multiple avatar formats:\n * - Direct HTTPS URLs: returned as-is\n * - IPFS URIs (ipfs://...): resolved via public gateway\n * - Raw IPFS CIDs (Qm..., bafy...): converted to IPFS gateway URLs\n * - Arweave URIs (ar://...): resolved via arweave.net\n * - NFT references (eip155:chainId/erc721:contract/tokenId): fetches tokenURI and extracts image\n * - Data URIs: returned as-is\n *\n * @param name - Full name including .mon\n * @param config - Optional client configuration\n * @param options - Optional fallback URL to return if avatar not found\n * @returns A usable HTTPS URL, or null (or fallback)\n *\n * @example\n * ```ts\n * const avatarUrl = await getAvatarUrl('alice.mon')\n * // 'https://example.com/avatar.png'\n *\n * // With fallback\n * const avatarUrl = await getAvatarUrl('alice.mon', undefined, {\n * fallback: DEFAULT_AVATAR_PLACEHOLDER\n * })\n * ```\n */\nexport async function getAvatarUrl(\n name: string,\n config?: MNSClientConfig,\n options?: { fallback?: string },\n): Promise<string | null> {\n const raw = await getTextRecord(name, 'avatar', config);\n if (!raw) return options?.fallback || null;\n\n try {\n // Direct HTTP(S) URL\n if (raw.startsWith('http://') || raw.startsWith('https://')) {\n return raw;\n }\n\n // IPFS with protocol\n if (raw.startsWith('ipfs://')) {\n const hash = raw.slice(7);\n return `https://ipfs.io/ipfs/${hash}`;\n }\n\n // Raw IPFS CID (Qm... or bafy...)\n if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(raw)) {\n return `https://ipfs.io/ipfs/${raw}`;\n }\n\n // Arweave\n if (raw.startsWith('ar://')) {\n const hash = raw.slice(5);\n return `https://arweave.net/${hash}`;\n }\n\n // NFT reference: eip155:{chainId}/erc721:{contract}/{tokenId}\n // or eip155:{chainId}/erc1155:{contract}/{tokenId}\n const nftMatch = raw.match(\n /^eip155:(\\d+)\\/(erc721|erc1155):0x([a-fA-F0-9]{40})\\/(\\d+)$/,\n );\n if (nftMatch) {\n const client = getMNSClient(config);\n const contract = `0x${nftMatch[3]}` as `0x${string}`;\n const tokenId = BigInt(nftMatch[4]);\n\n const tokenUriAbi = [\n {\n name: nftMatch[2] === 'erc721' ? 'tokenURI' : 'uri',\n type: 'function',\n inputs: [{ name: 'tokenId', type: 'uint256' }],\n outputs: [{ name: '', type: 'string' }],\n stateMutability: 'view',\n },\n ] as const;\n\n let tokenUri = (await client.readContract({\n address: contract,\n abi: tokenUriAbi,\n functionName: nftMatch[2] === 'erc721' ? 'tokenURI' : 'uri',\n args: [tokenId],\n })) as string;\n\n // Resolve IPFS tokenURI\n if (tokenUri.startsWith('ipfs://')) {\n tokenUri = `https://ipfs.io/ipfs/${tokenUri.slice(7)}`;\n }\n\n // Fetch metadata JSON and extract image\n const res = await fetch(tokenUri);\n const metadata = await res.json();\n let image = metadata.image || metadata.image_url || null;\n\n if (image && image.startsWith('ipfs://')) {\n image = `https://ipfs.io/ipfs/${image.slice(7)}`;\n }\n\n return image || options?.fallback || null;\n }\n\n // data: URIs\n if (raw.startsWith('data:')) {\n return raw;\n }\n\n return options?.fallback || null;\n } catch (error) {\n console.warn('Failed to resolve avatar:', error);\n return options?.fallback || null;\n }\n}\n\n/**\n * Clear the internal resolution cache.\n * Useful after a new registration.\n */\nexport function clearCache() {\n nameCache.clear();\n addrCache.clear();\n}\n","import {\n createPublicClient,\n http,\n type PublicClient,\n type Chain,\n type Transport,\n} from 'viem';\nimport { monad } from 'viem/chains';\n\nexport type MNSClientConfig = {\n /** Custom RPC URL. Defaults to the public Monad RPC. */\n rpcUrl?: string;\n /** Existing viem PublicClient to reuse instead of creating a new one. */\n client?: PublicClient<Transport, Chain>;\n};\n\nlet defaultClient: PublicClient<Transport, Chain> | null = null;\n\n/**\n * Get or create the MNS public client.\n * Reuses a singleton by default; pass config to override.\n */\nexport function getMNSClient(\n config?: MNSClientConfig,\n): PublicClient<Transport, Chain> {\n if (config?.client) return config.client;\n\n if (config?.rpcUrl) {\n return createPublicClient({\n chain: monad,\n transport: http(config.rpcUrl),\n });\n }\n\n if (!defaultClient) {\n defaultClient = createPublicClient({\n chain: monad,\n transport: http(),\n });\n }\n\n return defaultClient;\n}\n","export const MNS_REGISTRY =\n '0x13f963486e741c8d3fcdc0a34a910920339a19ce' as const;\nexport const MNS_PUBLIC_RESOLVER =\n '0xa2eb94c88e55d944aced2066c5cec9b759801f97' as const;\nexport const MNS_CONTROLLER =\n '0x98866c55adbc73ec6c272bb3604ddbdee3f282a8' as const;\nexport const MNS_BASE_REGISTRAR =\n '0x104a49db9318c284d462841b6940bdb46624ca55' as const;\n\nexport const ZERO_ADDRESS =\n '0x0000000000000000000000000000000000000000' as const;\n\n/**\n * Maximum recommended avatar size in bytes (50KB)\n * Strictly enforced for data URIs, recommended for all avatars\n */\nexport const MAX_AVATAR_BYTES = 51_200; // 50KB\n\n/**\n * Default avatar placeholder (1x1 transparent SVG)\n */\nexport const DEFAULT_AVATAR_PLACEHOLDER =\n 'data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\"%3E%3C/svg%3E';\n\nexport const registryAbi = [\n {\n name: 'resolver',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'address' }],\n stateMutability: 'view',\n },\n {\n name: 'owner',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'address' }],\n stateMutability: 'view',\n },\n] as const;\n\nexport const resolverAbi = [\n {\n name: 'addr',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'address' }],\n stateMutability: 'view',\n },\n {\n name: 'name',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'string' }],\n stateMutability: 'view',\n },\n {\n name: 'text',\n type: 'function',\n inputs: [\n { name: 'node', type: 'bytes32' },\n { name: 'key', type: 'string' },\n ],\n outputs: [{ name: '', type: 'string' }],\n stateMutability: 'view',\n },\n] as const;\n","import { namehash } from 'viem';\nimport { getMNSClient, type MNSClientConfig } from './client';\nimport { MNS_CONTROLLER, MNS_PUBLIC_RESOLVER } from './constants';\nimport { validateAvatarUri } from './utils';\n\n/**\n * MNS Write Helpers\n *\n * These return transaction parameters ({ address, abi, functionName, args, value })\n * that you pass to wagmi's writeContract or any wallet client.\n * The user signs with their own wallet.\n */\n\n// ─── ABIs ──────────────────────────────────────────────────────\n\nexport const controllerReadAbi = [\n {\n name: 'available',\n type: 'function',\n inputs: [{ name: 'label', type: 'string' }],\n outputs: [{ name: '', type: 'bool' }],\n stateMutability: 'view',\n },\n {\n name: 'rentPrice',\n type: 'function',\n inputs: [\n { name: 'label', type: 'string' },\n { name: 'duration', type: 'uint256' },\n ],\n outputs: [{ name: '', type: 'uint256' }],\n stateMutability: 'view',\n },\n {\n name: 'hasClaimedFreebie',\n type: 'function',\n inputs: [{ name: 'user', type: 'address' }],\n outputs: [{ name: '', type: 'bool' }],\n stateMutability: 'view',\n },\n] as const;\n\nexport const controllerWriteAbi = [\n {\n name: 'register',\n type: 'function',\n inputs: [\n { name: 'label', type: 'string' },\n { name: 'owner', type: 'address' },\n { name: 'duration', type: 'uint256' },\n ],\n outputs: [{ name: '', type: 'uint256' }],\n stateMutability: 'payable',\n },\n] as const;\n\nexport const resolverWriteAbi = [\n {\n name: 'setText',\n type: 'function',\n inputs: [\n { name: 'node', type: 'bytes32' },\n { name: 'key', type: 'string' },\n { name: 'value', type: 'string' },\n ],\n outputs: [],\n stateMutability: 'nonpayable',\n },\n {\n name: 'setAddr',\n type: 'function',\n inputs: [\n { name: 'node', type: 'bytes32' },\n { name: 'a', type: 'address' },\n ],\n outputs: [],\n stateMutability: 'nonpayable',\n },\n] as const;\n\n// ─── Types ─────────────────────────────────────────────────────\n\nexport type RegistrationInfo = {\n available: boolean;\n price: bigint;\n isFreebie: boolean;\n hasClaimed: boolean;\n duration: bigint;\n label: string;\n};\n\n// ─── Registration ──────────────────────────────────────────────\n\nconst ONE_YEAR = BigInt(365 * 24 * 60 * 60);\n\n/**\n * Get all info needed before registration.\n *\n * @example\n * ```ts\n * const info = await getRegistrationInfo('alice', userAddress)\n * // { available: true, price: 96831564n..., isFreebie: true, ... }\n * ```\n */\nexport async function getRegistrationInfo(\n label: string,\n userAddress: string,\n durationYears = 1,\n config?: MNSClientConfig,\n): Promise<RegistrationInfo> {\n const client = getMNSClient(config);\n const duration = ONE_YEAR * BigInt(durationYears);\n\n const [available, price, hasClaimed, balance] = await Promise.all([\n client.readContract({\n address: MNS_CONTROLLER,\n abi: controllerReadAbi,\n functionName: 'available',\n args: [label],\n }),\n client.readContract({\n address: MNS_CONTROLLER,\n abi: controllerReadAbi,\n functionName: 'rentPrice',\n args: [label, duration],\n }),\n client.readContract({\n address: MNS_CONTROLLER,\n abi: controllerReadAbi,\n functionName: 'hasClaimedFreebie',\n args: [userAddress as `0x${string}`],\n }),\n client.getBalance({ address: userAddress as `0x${string}` }),\n ]);\n\n const isFreebie = balance >= 10n * 10n ** 18n && !hasClaimed;\n\n return { available, price, isFreebie, hasClaimed, duration, label };\n}\n\n/**\n * Get transaction parameters for registering a .mon name.\n * Pass the result to wagmi's writeContract.\n *\n * @example\n * ```ts\n * const tx = await getRegisterTx('alice', userAddress)\n * writeContract(tx)\n * ```\n */\nexport async function getRegisterTx(\n label: string,\n ownerAddress: string,\n durationYears = 1,\n config?: MNSClientConfig,\n) {\n const info = await getRegistrationInfo(\n label,\n ownerAddress,\n durationYears,\n config,\n );\n\n if (!info.available) {\n throw new Error(`${label}.mon is not available`);\n }\n\n return {\n address: MNS_CONTROLLER,\n abi: controllerWriteAbi,\n functionName: 'register' as const,\n args: [label, ownerAddress as `0x${string}`, info.duration] as const,\n value: info.isFreebie ? 0n : info.price,\n gas: 3_000_000n,\n _meta: {\n label,\n isFreebie: info.isFreebie,\n price: info.price,\n duration: info.duration,\n },\n };\n}\n\n// ─── Text Records ──────────────────────────────────────────────\n\n/**\n * Get transaction parameters for setting a text record.\n * The connected user must own the name.\n * For avatar records, validates data URI size constraints.\n *\n * @example\n * ```ts\n * const tx = getSetTextTx('alice.mon', 'avatar', 'https://example.com/pic.png')\n * writeContract(tx)\n * ```\n */\nexport function getSetTextTx(name: string, key: string, value: string) {\n // Enforce strict validation for avatar data URIs\n if (key === 'avatar' && value.startsWith('data:')) {\n const validation = validateAvatarUri(value);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n }\n\n // Log warning for non-validated remote avatars\n if (key === 'avatar' && !value.startsWith('data:')) {\n console.warn(\n '⚠️ Avatar set without size validation. For best practices, use setAvatarValidated() or validateAvatarFull() before calling setAvatar().',\n );\n }\n\n const node = namehash(\n name.endsWith('.mon') ? name : `${name}.mon`,\n ) as `0x${string}`;\n\n return {\n address: MNS_PUBLIC_RESOLVER,\n abi: resolverWriteAbi,\n functionName: 'setText' as const,\n args: [node, key, value] as const,\n };\n}\n\n/**\n * Get transaction parameters for setting the address record.\n */\nexport function getSetAddrTx(name: string, addr: string) {\n const node = namehash(\n name.endsWith('.mon') ? name : `${name}.mon`,\n ) as `0x${string}`;\n\n return {\n address: MNS_PUBLIC_RESOLVER,\n abi: resolverWriteAbi,\n functionName: 'setAddr' as const,\n args: [node, addr as `0x${string}`] as const,\n };\n}\n\nexport const TEXT_RECORD_KEYS = {\n avatar: 'avatar',\n url: 'url',\n description: 'description',\n email: 'email',\n twitter: 'com.twitter',\n github: 'com.github',\n discord: 'com.discord',\n telegram: 'org.telegram',\n} as const;\n","import { MAX_AVATAR_BYTES } from './constants';\n\n/**\n * Validate avatar URI format and size (for data URIs)\n * This performs synchronous validation only\n */\nexport function validateAvatarUri(uri: string): {\n valid: boolean;\n error?: string;\n sizeBytes?: number;\n} {\n if (!uri) {\n return { valid: false, error: 'Avatar URI is required' };\n }\n\n // STRICT: Data URIs go on-chain, must be limited\n if (uri.startsWith('data:')) {\n const sizeBytes = new Blob([uri]).size;\n\n if (sizeBytes > MAX_AVATAR_BYTES) {\n return {\n valid: false,\n error: `Avatar size (${(sizeBytes / 1024).toFixed(1)}KB) exceeds ${MAX_AVATAR_BYTES / 1024}KB limit. Please optimize as WebP or SVG for better performance.`,\n sizeBytes,\n };\n }\n\n return { valid: true, sizeBytes };\n }\n\n // For IPFS/HTTP, validate format only (size check is async)\n const validSchemes = ['http://', 'https://', 'ipfs://', 'eip155:', 'ar://'];\n const hasValidScheme = validSchemes.some((scheme) => uri.startsWith(scheme));\n\n if (!hasValidScheme) {\n // Check if it's a raw IPFS CID\n if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(uri)) {\n return { valid: true };\n }\n\n return {\n valid: false,\n error: 'Avatar must be a valid HTTP, IPFS, Arweave, or NFT URI',\n };\n }\n\n return { valid: true };\n}\n\n/**\n * Validate avatar including remote file size check\n * Use this in UI before calling setAvatar() for comprehensive validation\n *\n * @throws Error if avatar is invalid or too large\n *\n * @example\n * ```ts\n * try {\n * await validateAvatarFull(avatarUrl);\n * // Safe to use\n * } catch (error) {\n * console.error(error.message);\n * // \"Avatar file size (125.3KB) exceeds 50KB limit...\"\n * }\n * ```\n */\nexport async function validateAvatarFull(\n uri: string,\n options?: { maxBytes?: number; timeout?: number },\n): Promise<{ valid: true; sizeBytes?: number }> {\n const maxBytes = options?.maxBytes || MAX_AVATAR_BYTES;\n const timeout = options?.timeout || 5000;\n\n // First, validate format\n const formatValidation = validateAvatarUri(uri);\n if (!formatValidation.valid) {\n throw new Error(formatValidation.error);\n }\n\n // Data URIs already validated\n if (uri.startsWith('data:')) {\n return { valid: true, sizeBytes: formatValidation.sizeBytes };\n }\n\n // NFT avatars - can't pre-validate size easily\n if (uri.startsWith('eip155:')) {\n return { valid: true };\n }\n\n // For HTTP/IPFS, try to check size via HEAD request\n let checkUrl = uri;\n\n // Convert IPFS to gateway URL for size check\n if (uri.startsWith('ipfs://')) {\n checkUrl = `https://ipfs.io/ipfs/${uri.slice(7)}`;\n } else if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(uri)) {\n checkUrl = `https://ipfs.io/ipfs/${uri}`;\n } else if (uri.startsWith('ar://')) {\n checkUrl = `https://arweave.net/${uri.slice(5)}`;\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch(checkUrl, {\n method: 'HEAD',\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const contentLength = response.headers.get('content-length');\n if (contentLength) {\n const sizeBytes = parseInt(contentLength, 10);\n\n if (sizeBytes > maxBytes) {\n throw new Error(\n `Avatar file size (${(sizeBytes / 1024).toFixed(1)}KB) exceeds ${maxBytes / 1024}KB limit. Please optimize as WebP or SVG for better performance.`,\n );\n }\n\n return { valid: true, sizeBytes };\n }\n\n // No content-length header - warn but allow\n console.warn(\n `Could not determine avatar size for ${uri}. Ensure it's under ${maxBytes / 1024}KB.`,\n );\n return { valid: true };\n } catch (error: any) {\n if (error.name === 'AbortError') {\n console.warn(`Avatar size check timed out for ${uri}`);\n return { valid: true };\n }\n\n // If it's our size error, re-throw it\n if (error.message.includes('exceeds')) {\n throw error;\n }\n\n // Network error - warn but don't block\n console.warn(`Could not check avatar size: ${error.message}`);\n return { valid: true };\n }\n}\n\n/**\n * Get file size of a remote URL (helper for UI validation)\n * Returns size in bytes or null if unable to determine\n *\n * @example\n * ```ts\n * const size = await getRemoteAvatarSize('https://example.com/avatar.png');\n * if (size && size > MAX_AVATAR_BYTES) {\n * alert('Avatar too large!');\n * }\n * ```\n */\nexport async function getRemoteAvatarSize(url: string): Promise<number | null> {\n try {\n const response = await fetch(url, { method: 'HEAD' });\n const contentLength = response.headers.get('content-length');\n return contentLength ? parseInt(contentLength, 10) : null;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["namehash","namehash"]}
|