@monadns/sdk 2.2.0 → 2.3.1

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 CHANGED
@@ -15,10 +15,10 @@ import { resolveName, lookupAddress } from '@monadns/sdk';
15
15
 
16
16
  // Forward: name → address
17
17
  const address = await resolveName('alice.mon');
18
- // '0xa1238BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47'
18
+ // '0xa05a8BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47'
19
19
 
20
20
  // Reverse: address → name
21
- const name = await lookupAddress('0xa1238BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47');
21
+ const name = await lookupAddress('0xa05a8BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47');
22
22
  // 'alice.mon'
23
23
  ```
24
24
 
@@ -144,7 +144,11 @@ import { clearCache } from '@monadns/sdk';
144
144
  function RegisterPage() {
145
145
  const [label, setLabel] = useState('');
146
146
  const { address } = useAccount();
147
- const { info, loading: checking } = useRegistrationInfo(label, address);
147
+ const {
148
+ info,
149
+ loading: checking,
150
+ error: checkError,
151
+ } = useRegistrationInfo(label, address);
148
152
  const { prepare, loading: preparing, error } = useMNSRegister();
149
153
  const { writeContract, data: hash } = useWriteContract();
150
154
  const { isLoading: confirming, isSuccess } = useWaitForTransactionReceipt({
@@ -171,8 +175,9 @@ function RegisterPage() {
171
175
  />
172
176
 
173
177
  {checking && <p className="text-gray-400">Checking availability...</p>}
178
+ {checkError && <p className="text-red-500">❌ {checkError}</p>}
174
179
 
175
- {info && !checking && (
180
+ {info && !checking && !checkError && (
176
181
  <div className="space-y-2">
177
182
  <p>{info.available ? '✅ Available!' : '❌ Already taken'}</p>
178
183
 
@@ -303,6 +308,32 @@ function ProfileEditor({ name }: { name: string }) {
303
308
  }
304
309
  ```
305
310
 
311
+ ## Name Validation
312
+
313
+ The SDK automatically validates names and provides clear error messages:
314
+
315
+ ```typescript
316
+ import { validateMonName } from '@monadns/sdk';
317
+
318
+ const validation = validateMonName('alice');
319
+ if (!validation.valid) {
320
+ console.error(validation.error);
321
+ }
322
+
323
+ // Invalid examples that are rejected:
324
+ validateMonName('0x123'); // ❌ "Name cannot start with "0x" (reserved for addresses)"
325
+ validateMonName('hello--world'); // ❌ "Name cannot contain consecutive hyphens"
326
+ validateMonName('-alice'); // ❌ "Name can only contain lowercase letters..."
327
+ validateMonName('a'.repeat(64)); // ❌ "Name must be 63 characters or less"
328
+ validateMonName(''); // ❌ "Name cannot be empty"
329
+
330
+ // Valid examples:
331
+ validateMonName('alice'); // ✅
332
+ validateMonName('a'); // ✅ 1-character names allowed
333
+ validateMonName('ab'); // ✅ 2-character names allowed
334
+ validateMonName('web3-name'); // ✅ Hyphens allowed (not at start/end)
335
+ ```
336
+
306
337
  ## Avatar Best Practices
307
338
 
308
339
  ### Recommended Workflow (Enforced by SDK)
@@ -481,6 +512,7 @@ if (available) {
481
512
  | `getRegisterTx(label, address)` | Get tx params for registration |
482
513
  | `getSetTextTx(name, key, value)` | Get tx params for setting a text record |
483
514
  | `getSetAddrTx(name, address)` | Get tx params for updating the address record |
515
+ | `validateMonName(label)` | Validate .mon name format and rules |
484
516
  | `validateAvatarUri(uri)` | Validate avatar format and data URI size |
485
517
  | `validateAvatarFull(uri)` | Async validation including remote file size |
486
518
  | `getRemoteAvatarSize(url)` | Get file size of remote URL (helper for UI) |
@@ -546,9 +578,26 @@ const name = await lookupAddress('0x...', { client });
546
578
 
547
579
  **Chain:** Monad Mainnet (Chain ID 143)
548
580
 
549
- ## Migrating from v2.1 to v2.2
581
+ ## Changelog
582
+
583
+ ### v2.3.0
584
+
585
+ **New Features:**
586
+
587
+ - **Name validation**: Added `validateMonName()` function with comprehensive validation rules
588
+ - **1 & 2-character names**: Removed artificial 3-character minimum - all lengths now supported
589
+ - **0x prefix protection**: Names cannot start with "0x" to prevent confusion with addresses
590
+ - **Automatic validation**: `useRegistrationInfo()` and write functions now validate names automatically
550
591
 
551
- ### New Features
592
+ **Improvements:**
593
+
594
+ - Better error messages for invalid names
595
+ - Validation happens client-side before RPC calls (faster feedback)
596
+ - Consistent validation across all entry points
597
+
598
+ ### v2.2.0
599
+
600
+ **New Features:**
552
601
 
553
602
  - **Avatar validation**: `validateAvatarUri()` and `validateAvatarFull()` for size checking
554
603
  - **Avatar constants**: `MAX_AVATAR_BYTES` (50KB limit) and `DEFAULT_AVATAR_PLACEHOLDER`
@@ -557,37 +606,27 @@ const name = await lookupAddress('0x...', { client });
557
606
  - **Size helper**: `getRemoteAvatarSize()` for checking remote file sizes
558
607
  - **Fallback support**: `getAvatarUrl()` now accepts fallback option
559
608
 
560
- ### Behavior Changes
609
+ **Behavior Changes:**
561
610
 
562
611
  - Data URI avatars are now strictly limited to 50KB (throws error if exceeded)
563
612
  - Setting non-data-URI avatars without validation logs a warning
564
613
  - `getAvatarUrl()` now handles raw IPFS CIDs (e.g., `QmX...`) automatically
565
614
 
566
- ## Migrating from v2.0 to v2.1
567
-
568
- ### Breaking Changes
615
+ ### v2.1.0
569
616
 
570
- **`isRegistered()` has been renamed to `getAvailable()` with inverted logic**
617
+ **New Features:**
571
618
 
572
- **Before (v2.0):**
573
-
574
- ```typescript
575
- const registered = await isRegistered('alice.mon');
576
- if (registered) {
577
- console.log('Name is taken');
578
- }
579
- ```
619
+ - `getOwner(name)` - Get the owner of a name
620
+ - `getResolver(name)` - Get the resolver contract
621
+ - `getExpiry(name)` - Get expiration timestamp
622
+ - `useMNSOwner(name)` - React hook for ownership info
623
+ - `useMNSResolver(name)` - React hook for resolver info
624
+ - `useMNSExpiry(name)` - React hook for expiry info with computed fields
625
+ - `MNS_BASE_REGISTRAR` constant now exported
580
626
 
581
- **After (v2.1+):**
627
+ **Breaking Changes:**
582
628
 
583
- ```typescript
584
- const available = await getAvailable('alice.mon');
585
- if (available) {
586
- console.log('Name is available for registration');
587
- } else {
588
- console.log('Name is taken');
589
- }
590
- ```
629
+ - `isRegistered()` → `getAvailable()` (inverted boolean logic)
591
630
 
592
631
  ## Compatibility
593
632
 
package/dist/index.cjs CHANGED
@@ -48,7 +48,8 @@ __export(src_exports, {
48
48
  resolveName: () => resolveName,
49
49
  resolverWriteAbi: () => resolverWriteAbi,
50
50
  validateAvatarFull: () => validateAvatarFull,
51
- validateAvatarUri: () => validateAvatarUri
51
+ validateAvatarUri: () => validateAvatarUri,
52
+ validateMonName: () => validateMonName
52
53
  });
53
54
  module.exports = __toCommonJS(src_exports);
54
55
 
@@ -361,6 +362,31 @@ function clearCache() {
361
362
  var import_viem3 = require("viem");
362
363
 
363
364
  // src/utils.ts
365
+ function validateMonName(label) {
366
+ if (!label || label.trim().length === 0) {
367
+ return { valid: false, error: "Name cannot be empty" };
368
+ }
369
+ const trimmed = label.trim().toLowerCase();
370
+ if (trimmed.startsWith("0x")) {
371
+ return {
372
+ valid: false,
373
+ error: 'Name cannot start with "0x" (reserved for addresses)'
374
+ };
375
+ }
376
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(trimmed)) {
377
+ return {
378
+ valid: false,
379
+ error: "Name can only contain lowercase letters, numbers, and hyphens (not at start/end)"
380
+ };
381
+ }
382
+ if (trimmed.includes("--")) {
383
+ return { valid: false, error: "Name cannot contain consecutive hyphens" };
384
+ }
385
+ if (trimmed.length > 63) {
386
+ return { valid: false, error: "Name must be 63 characters or less" };
387
+ }
388
+ return { valid: true };
389
+ }
364
390
  function validateAvatarUri(uri) {
365
391
  if (!uri) {
366
392
  return { valid: false, error: "Avatar URI is required" };
@@ -519,6 +545,10 @@ var resolverWriteAbi = [
519
545
  ];
520
546
  var ONE_YEAR = BigInt(365 * 24 * 60 * 60);
521
547
  async function getRegistrationInfo(label, userAddress, durationYears = 1, config) {
548
+ const validation = validateMonName(label);
549
+ if (!validation.valid) {
550
+ throw new Error(validation.error);
551
+ }
522
552
  const client = getMNSClient(config);
523
553
  const duration = ONE_YEAR * BigInt(durationYears);
524
554
  const [available, price, hasClaimed, balance] = await Promise.all([
@@ -546,6 +576,10 @@ async function getRegistrationInfo(label, userAddress, durationYears = 1, config
546
576
  return { available, price, isFreebie, hasClaimed, duration, label };
547
577
  }
548
578
  async function getRegisterTx(label, ownerAddress, durationYears = 1, config) {
579
+ const validation = validateMonName(label);
580
+ if (!validation.valid) {
581
+ throw new Error(validation.error);
582
+ }
549
583
  const info = await getRegistrationInfo(
550
584
  label,
551
585
  ownerAddress,
@@ -643,6 +677,7 @@ var TEXT_RECORD_KEYS = {
643
677
  resolveName,
644
678
  resolverWriteAbi,
645
679
  validateAvatarFull,
646
- validateAvatarUri
680
+ validateAvatarUri,
681
+ validateMonName
647
682
  });
648
683
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/resolve.ts","../src/client.ts","../src/constants.ts","../src/write.ts","../src/utils.ts"],"sourcesContent":["// Core resolution (read-only)\nexport {\n resolveName,\n lookupAddress,\n getTextRecord,\n getAvatarUrl,\n getDisplayName,\n resolveInput,\n getAvailable,\n getOwner,\n getResolver,\n getExpiry,\n clearCache,\n} from './resolve';\n\n// Write helpers\nexport {\n getRegistrationInfo,\n getRegisterTx,\n getSetTextTx,\n getSetAddrTx,\n TEXT_RECORD_KEYS,\n controllerReadAbi,\n controllerWriteAbi,\n resolverWriteAbi,\n} from './write';\nexport type { RegistrationInfo } from './write';\n\n// Client config\nexport { getMNSClient } from './client';\nexport type { MNSClientConfig } from './client';\n\n// Constants\nexport {\n MNS_REGISTRY,\n MNS_PUBLIC_RESOLVER,\n MNS_CONTROLLER,\n MNS_BASE_REGISTRAR,\n MAX_AVATAR_BYTES,\n DEFAULT_AVATAR_PLACEHOLDER,\n} from './constants';\n\n// Utilities\nexport {\n validateAvatarUri,\n validateAvatarFull,\n getRemoteAvatarSize,\n} from './utils';\n","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":["import_viem","import_viem"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/resolve.ts","../src/client.ts","../src/constants.ts","../src/write.ts","../src/utils.ts"],"sourcesContent":["// Core resolution (read-only)\nexport {\n resolveName,\n lookupAddress,\n getTextRecord,\n getAvatarUrl,\n getDisplayName,\n resolveInput,\n getAvailable,\n getOwner,\n getResolver,\n getExpiry,\n clearCache,\n} from './resolve';\n\n// Write helpers\nexport {\n getRegistrationInfo,\n getRegisterTx,\n getSetTextTx,\n getSetAddrTx,\n TEXT_RECORD_KEYS,\n controllerReadAbi,\n controllerWriteAbi,\n resolverWriteAbi,\n} from './write';\nexport type { RegistrationInfo } from './write';\n\n// Client config\nexport { getMNSClient } from './client';\nexport type { MNSClientConfig } from './client';\n\n// Constants\nexport {\n MNS_REGISTRY,\n MNS_PUBLIC_RESOLVER,\n MNS_CONTROLLER,\n MNS_BASE_REGISTRAR,\n MAX_AVATAR_BYTES,\n DEFAULT_AVATAR_PLACEHOLDER,\n} from './constants';\n\n// Utilities\nexport {\n validateAvatarUri,\n validateAvatarFull,\n getRemoteAvatarSize,\n validateMonName,\n} from './utils';\n","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, validateMonName } 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 // Validate name format before making RPC calls\n const validation = validateMonName(label);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\n\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 // Validate name format\n const validation = validateMonName(label);\n if (!validation.valid) {\n throw new Error(validation.error);\n }\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 a .mon name according to MNS rules\n *\n * @example\n * ```ts\n * const validation = validateMonName('alice');\n * if (!validation.valid) {\n * console.error(validation.error);\n * }\n * ```\n */\nexport function validateMonName(label: string): {\n valid: boolean;\n error?: string;\n} {\n if (!label || label.trim().length === 0) {\n return { valid: false, error: 'Name cannot be empty' };\n }\n\n const trimmed = label.trim().toLowerCase();\n\n // Cannot start with 0x (looks like an address)\n if (trimmed.startsWith('0x')) {\n return {\n valid: false,\n error: 'Name cannot start with \"0x\" (reserved for addresses)',\n };\n }\n\n // Check for valid characters (alphanumeric + hyphen)\n if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(trimmed)) {\n return {\n valid: false,\n error:\n 'Name can only contain lowercase letters, numbers, and hyphens (not at start/end)',\n };\n }\n\n // No consecutive hyphens\n if (trimmed.includes('--')) {\n return { valid: false, error: 'Name cannot contain consecutive hyphens' };\n }\n\n // Maximum length (ENS standard is 63 characters for a label)\n if (trimmed.length > 63) {\n return { valid: false, error: 'Name must be 63 characters or less' };\n }\n\n return { valid: true };\n}\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":["import_viem","import_viem"]}
package/dist/index.d.cts CHANGED
@@ -439,6 +439,21 @@ declare const MAX_AVATAR_BYTES = 51200;
439
439
  */
440
440
  declare const DEFAULT_AVATAR_PLACEHOLDER = "data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\"%3E%3C/svg%3E";
441
441
 
442
+ /**
443
+ * Validate a .mon name according to MNS rules
444
+ *
445
+ * @example
446
+ * ```ts
447
+ * const validation = validateMonName('alice');
448
+ * if (!validation.valid) {
449
+ * console.error(validation.error);
450
+ * }
451
+ * ```
452
+ */
453
+ declare function validateMonName(label: string): {
454
+ valid: boolean;
455
+ error?: string;
456
+ };
442
457
  /**
443
458
  * Validate avatar URI format and size (for data URIs)
444
459
  * This performs synchronous validation only
@@ -486,4 +501,4 @@ declare function validateAvatarFull(uri: string, options?: {
486
501
  */
487
502
  declare function getRemoteAvatarSize(url: string): Promise<number | null>;
488
503
 
489
- export { DEFAULT_AVATAR_PLACEHOLDER, MAX_AVATAR_BYTES, type MNSClientConfig, MNS_BASE_REGISTRAR, MNS_CONTROLLER, MNS_PUBLIC_RESOLVER, MNS_REGISTRY, type RegistrationInfo, TEXT_RECORD_KEYS, clearCache, controllerReadAbi, controllerWriteAbi, getAvailable, getAvatarUrl, getDisplayName, getExpiry, getMNSClient, getOwner, getRegisterTx, getRegistrationInfo, getRemoteAvatarSize, getResolver, getSetAddrTx, getSetTextTx, getTextRecord, lookupAddress, resolveInput, resolveName, resolverWriteAbi, validateAvatarFull, validateAvatarUri };
504
+ export { DEFAULT_AVATAR_PLACEHOLDER, MAX_AVATAR_BYTES, type MNSClientConfig, MNS_BASE_REGISTRAR, MNS_CONTROLLER, MNS_PUBLIC_RESOLVER, MNS_REGISTRY, type RegistrationInfo, TEXT_RECORD_KEYS, clearCache, controllerReadAbi, controllerWriteAbi, getAvailable, getAvatarUrl, getDisplayName, getExpiry, getMNSClient, getOwner, getRegisterTx, getRegistrationInfo, getRemoteAvatarSize, getResolver, getSetAddrTx, getSetTextTx, getTextRecord, lookupAddress, resolveInput, resolveName, resolverWriteAbi, validateAvatarFull, validateAvatarUri, validateMonName };
package/dist/index.d.ts CHANGED
@@ -439,6 +439,21 @@ declare const MAX_AVATAR_BYTES = 51200;
439
439
  */
440
440
  declare const DEFAULT_AVATAR_PLACEHOLDER = "data:image/svg+xml,%3Csvg xmlns=\"http://www.w3.org/2000/svg\"%3E%3C/svg%3E";
441
441
 
442
+ /**
443
+ * Validate a .mon name according to MNS rules
444
+ *
445
+ * @example
446
+ * ```ts
447
+ * const validation = validateMonName('alice');
448
+ * if (!validation.valid) {
449
+ * console.error(validation.error);
450
+ * }
451
+ * ```
452
+ */
453
+ declare function validateMonName(label: string): {
454
+ valid: boolean;
455
+ error?: string;
456
+ };
442
457
  /**
443
458
  * Validate avatar URI format and size (for data URIs)
444
459
  * This performs synchronous validation only
@@ -486,4 +501,4 @@ declare function validateAvatarFull(uri: string, options?: {
486
501
  */
487
502
  declare function getRemoteAvatarSize(url: string): Promise<number | null>;
488
503
 
489
- export { DEFAULT_AVATAR_PLACEHOLDER, MAX_AVATAR_BYTES, type MNSClientConfig, MNS_BASE_REGISTRAR, MNS_CONTROLLER, MNS_PUBLIC_RESOLVER, MNS_REGISTRY, type RegistrationInfo, TEXT_RECORD_KEYS, clearCache, controllerReadAbi, controllerWriteAbi, getAvailable, getAvatarUrl, getDisplayName, getExpiry, getMNSClient, getOwner, getRegisterTx, getRegistrationInfo, getRemoteAvatarSize, getResolver, getSetAddrTx, getSetTextTx, getTextRecord, lookupAddress, resolveInput, resolveName, resolverWriteAbi, validateAvatarFull, validateAvatarUri };
504
+ export { DEFAULT_AVATAR_PLACEHOLDER, MAX_AVATAR_BYTES, type MNSClientConfig, MNS_BASE_REGISTRAR, MNS_CONTROLLER, MNS_PUBLIC_RESOLVER, MNS_REGISTRY, type RegistrationInfo, TEXT_RECORD_KEYS, clearCache, controllerReadAbi, controllerWriteAbi, getAvailable, getAvatarUrl, getDisplayName, getExpiry, getMNSClient, getOwner, getRegisterTx, getRegistrationInfo, getRemoteAvatarSize, getResolver, getSetAddrTx, getSetTextTx, getTextRecord, lookupAddress, resolveInput, resolveName, resolverWriteAbi, validateAvatarFull, validateAvatarUri, validateMonName };
package/dist/index.js CHANGED
@@ -310,6 +310,31 @@ function clearCache() {
310
310
  import { namehash as namehash2 } from "viem";
311
311
 
312
312
  // src/utils.ts
313
+ function validateMonName(label) {
314
+ if (!label || label.trim().length === 0) {
315
+ return { valid: false, error: "Name cannot be empty" };
316
+ }
317
+ const trimmed = label.trim().toLowerCase();
318
+ if (trimmed.startsWith("0x")) {
319
+ return {
320
+ valid: false,
321
+ error: 'Name cannot start with "0x" (reserved for addresses)'
322
+ };
323
+ }
324
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(trimmed)) {
325
+ return {
326
+ valid: false,
327
+ error: "Name can only contain lowercase letters, numbers, and hyphens (not at start/end)"
328
+ };
329
+ }
330
+ if (trimmed.includes("--")) {
331
+ return { valid: false, error: "Name cannot contain consecutive hyphens" };
332
+ }
333
+ if (trimmed.length > 63) {
334
+ return { valid: false, error: "Name must be 63 characters or less" };
335
+ }
336
+ return { valid: true };
337
+ }
313
338
  function validateAvatarUri(uri) {
314
339
  if (!uri) {
315
340
  return { valid: false, error: "Avatar URI is required" };
@@ -468,6 +493,10 @@ var resolverWriteAbi = [
468
493
  ];
469
494
  var ONE_YEAR = BigInt(365 * 24 * 60 * 60);
470
495
  async function getRegistrationInfo(label, userAddress, durationYears = 1, config) {
496
+ const validation = validateMonName(label);
497
+ if (!validation.valid) {
498
+ throw new Error(validation.error);
499
+ }
471
500
  const client = getMNSClient(config);
472
501
  const duration = ONE_YEAR * BigInt(durationYears);
473
502
  const [available, price, hasClaimed, balance] = await Promise.all([
@@ -495,6 +524,10 @@ async function getRegistrationInfo(label, userAddress, durationYears = 1, config
495
524
  return { available, price, isFreebie, hasClaimed, duration, label };
496
525
  }
497
526
  async function getRegisterTx(label, ownerAddress, durationYears = 1, config) {
527
+ const validation = validateMonName(label);
528
+ if (!validation.valid) {
529
+ throw new Error(validation.error);
530
+ }
498
531
  const info = await getRegistrationInfo(
499
532
  label,
500
533
  ownerAddress,
@@ -591,6 +624,7 @@ export {
591
624
  resolveName,
592
625
  resolverWriteAbi,
593
626
  validateAvatarFull,
594
- validateAvatarUri
627
+ validateAvatarUri,
628
+ validateMonName
595
629
  };
596
630
  //# sourceMappingURL=index.js.map