@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/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @monadns/sdk
|
|
2
2
|
|
|
3
|
-
Resolve and register **.mon** names on the Monad network. The official SDK for the [Monad Name Service](https://monadscan.com).
|
|
3
|
+
Resolve and register **.mon** names on the Monad network. The official SDK for the [Monad Name Service](https://monadscan.com/address/0x98866c55adbc73ec6c272bb3604ddbdee3f282a8#readContract).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -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
|
-
// '
|
|
18
|
+
// '0xa1238BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47'
|
|
19
19
|
|
|
20
20
|
// Reverse: address → name
|
|
21
|
-
const name = await lookupAddress('
|
|
21
|
+
const name = await lookupAddress('0xa1238BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47');
|
|
22
22
|
// 'alice.mon'
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -92,6 +92,34 @@ function Twitter({ name }: { name: string }) {
|
|
|
92
92
|
}
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
### Check ownership and expiry
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { useMNSOwner, useMNSExpiry } from '@monadns/sdk/react';
|
|
99
|
+
|
|
100
|
+
function NameInfo({ name }: { name: string }) {
|
|
101
|
+
const { owner, loading: ownerLoading } = useMNSOwner(name);
|
|
102
|
+
const {
|
|
103
|
+
daysUntilExpiry,
|
|
104
|
+
isExpired,
|
|
105
|
+
loading: expiryLoading,
|
|
106
|
+
} = useMNSExpiry(name);
|
|
107
|
+
|
|
108
|
+
if (ownerLoading || expiryLoading) return <div>Loading...</div>;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div>
|
|
112
|
+
<p>Owner: {owner}</p>
|
|
113
|
+
{isExpired ? (
|
|
114
|
+
<p className="text-red-500">This name has expired!</p>
|
|
115
|
+
) : (
|
|
116
|
+
<p>Expires in {daysUntilExpiry} days</p>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
95
123
|
## Registration (with wagmi)
|
|
96
124
|
|
|
97
125
|
The SDK returns transaction parameters that work directly with wagmi's `useWriteContract`:
|
|
@@ -275,6 +303,163 @@ function ProfileEditor({ name }: { name: string }) {
|
|
|
275
303
|
}
|
|
276
304
|
```
|
|
277
305
|
|
|
306
|
+
## Avatar Best Practices
|
|
307
|
+
|
|
308
|
+
### Recommended Workflow (Enforced by SDK)
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
import {
|
|
312
|
+
validateAvatarFull,
|
|
313
|
+
useMNSTextRecords,
|
|
314
|
+
MAX_AVATAR_BYTES,
|
|
315
|
+
} from '@monadns/sdk/react';
|
|
316
|
+
import { useWriteContract } from 'wagmi';
|
|
317
|
+
|
|
318
|
+
function AvatarUploader({ name }: { name: string }) {
|
|
319
|
+
const [error, setError] = useState('');
|
|
320
|
+
const { setAvatarValidated } = useMNSTextRecords();
|
|
321
|
+
const { writeContract } = useWriteContract();
|
|
322
|
+
|
|
323
|
+
const handleUpload = async (url: string) => {
|
|
324
|
+
try {
|
|
325
|
+
setError('');
|
|
326
|
+
|
|
327
|
+
// SDK validates size automatically (throws if > 50KB)
|
|
328
|
+
const tx = await setAvatarValidated(name, url);
|
|
329
|
+
writeContract(tx);
|
|
330
|
+
} catch (e: any) {
|
|
331
|
+
setError(e.message);
|
|
332
|
+
// "Avatar file size (125.3KB) exceeds 50KB limit. Please optimize..."
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<div>
|
|
338
|
+
<input onChange={(e) => handleUpload(e.target.value)} />
|
|
339
|
+
{error && <p className="text-red-500">{error}</p>}
|
|
340
|
+
<p className="text-sm text-gray-500">
|
|
341
|
+
Max size: {(MAX_AVATAR_BYTES / 1024).toFixed(0)}KB (WebP/SVG
|
|
342
|
+
recommended)
|
|
343
|
+
</p>
|
|
344
|
+
</div>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Size Limits (Automatically Enforced)
|
|
350
|
+
|
|
351
|
+
- **Data URIs**: Strict 50KB limit (enforced immediately)
|
|
352
|
+
- **Remote URLs** (IPFS/HTTP): Validated when using `setAvatarValidated()`
|
|
353
|
+
- **NFT avatars**: No size check (resolved at display time)
|
|
354
|
+
|
|
355
|
+
### Supported Avatar Formats
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// ✅ HTTPS URLs
|
|
359
|
+
setAvatar('alice.mon', 'https://example.com/avatar.png');
|
|
360
|
+
|
|
361
|
+
// ✅ IPFS with protocol
|
|
362
|
+
setAvatar('alice.mon', 'ipfs://QmX...');
|
|
363
|
+
|
|
364
|
+
// ✅ Raw IPFS CID (auto-converted)
|
|
365
|
+
setAvatar('alice.mon', 'QmX...'); // → ipfs://QmX...
|
|
366
|
+
|
|
367
|
+
// ✅ Arweave
|
|
368
|
+
setAvatar('alice.mon', 'ar://abc123...');
|
|
369
|
+
|
|
370
|
+
// ✅ NFT avatar
|
|
371
|
+
setAvatar('alice.mon', 'eip155:1/erc721:0x.../123');
|
|
372
|
+
|
|
373
|
+
// ✅ Data URI (with size check)
|
|
374
|
+
setAvatar('alice.mon', 'data:image/svg+xml;base64,...');
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Manual Validation (Advanced)
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { validateAvatarUri, validateAvatarFull } from '@monadns/sdk';
|
|
381
|
+
|
|
382
|
+
// Quick format check (synchronous)
|
|
383
|
+
const validation = validateAvatarUri(avatarUrl);
|
|
384
|
+
if (!validation.valid) {
|
|
385
|
+
console.error(validation.error);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Full validation including size check (async)
|
|
389
|
+
try {
|
|
390
|
+
await validateAvatarFull(avatarUrl);
|
|
391
|
+
// Safe to use
|
|
392
|
+
} catch (error) {
|
|
393
|
+
console.error(error.message);
|
|
394
|
+
// "Avatar file size (65.3KB) exceeds 50KB limit..."
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Check remote file size
|
|
398
|
+
import { getRemoteAvatarSize, MAX_AVATAR_BYTES } from '@monadns/sdk';
|
|
399
|
+
|
|
400
|
+
const size = await getRemoteAvatarSize('https://example.com/avatar.png');
|
|
401
|
+
if (size && size > MAX_AVATAR_BYTES) {
|
|
402
|
+
alert(`Avatar too large: ${(size / 1024).toFixed(1)}KB. Max is 50KB.`);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Fallback Avatars
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { getAvatarUrl, DEFAULT_AVATAR_PLACEHOLDER } from '@monadns/sdk';
|
|
410
|
+
|
|
411
|
+
// Use fallback if avatar not set or fails to resolve
|
|
412
|
+
const avatar = await getAvatarUrl('alice.mon', undefined, {
|
|
413
|
+
fallback: DEFAULT_AVATAR_PLACEHOLDER,
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Override Validation (Not Recommended)
|
|
418
|
+
|
|
419
|
+
If you need to bypass validation (not recommended for production):
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// Use basic setAvatar() - only validates data URIs
|
|
423
|
+
const tx = setAvatar(name, largeAvatarUrl); // Logs warning
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Advanced Usage
|
|
427
|
+
|
|
428
|
+
### Check Name Ownership & Expiry
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import { getOwner, getExpiry, getResolver } from '@monadns/sdk';
|
|
432
|
+
|
|
433
|
+
// Check who owns a name
|
|
434
|
+
const owner = await getOwner('alice.mon');
|
|
435
|
+
console.log('Owner:', owner);
|
|
436
|
+
|
|
437
|
+
// Check when it expires
|
|
438
|
+
const expiry = await getExpiry('alice.mon');
|
|
439
|
+
const expiryDate = new Date(expiry * 1000);
|
|
440
|
+
const daysLeft = Math.floor(
|
|
441
|
+
(expiry * 1000 - Date.now()) / (1000 * 60 * 60 * 24),
|
|
442
|
+
);
|
|
443
|
+
console.log(`Expires: ${expiryDate.toLocaleDateString()} (${daysLeft} days)`);
|
|
444
|
+
|
|
445
|
+
// Get resolver contract
|
|
446
|
+
const resolver = await getResolver('alice.mon');
|
|
447
|
+
console.log('Resolver:', resolver);
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Check Availability Before Registration
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import { getAvailable } from '@monadns/sdk';
|
|
454
|
+
|
|
455
|
+
const available = await getAvailable('alice.mon');
|
|
456
|
+
if (available) {
|
|
457
|
+
console.log('alice.mon is available for registration!');
|
|
458
|
+
} else {
|
|
459
|
+
console.log('alice.mon is already taken');
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
278
463
|
## API Reference
|
|
279
464
|
|
|
280
465
|
### Core Functions (`@monadns/sdk`)
|
|
@@ -286,28 +471,48 @@ function ProfileEditor({ name }: { name: string }) {
|
|
|
286
471
|
| `getDisplayName(address)` | Returns `.mon` name or truncated address |
|
|
287
472
|
| `resolveInput(input)` | Accepts name or address, returns address |
|
|
288
473
|
| `getTextRecord(name, key)` | Get text record (avatar, url, etc.) |
|
|
289
|
-
| `getAvatarUrl(name)`
|
|
290
|
-
| `
|
|
474
|
+
| `getAvatarUrl(name, config, options)` | Get resolved avatar URL (handles IPFS, NFTs) |
|
|
475
|
+
| `getAvailable(name)` | Check if a name is available for registration |
|
|
476
|
+
| `getOwner(name)` | Get the owner address of a name |
|
|
477
|
+
| `getResolver(name)` | Get the resolver contract address |
|
|
478
|
+
| `getExpiry(name)` | Get expiry timestamp (Unix seconds) |
|
|
291
479
|
| `clearCache()` | Clear the resolution cache |
|
|
292
480
|
| `getRegistrationInfo(label, address)` | Get price, availability, freebie status |
|
|
293
481
|
| `getRegisterTx(label, address)` | Get tx params for registration |
|
|
294
482
|
| `getSetTextTx(name, key, value)` | Get tx params for setting a text record |
|
|
295
483
|
| `getSetAddrTx(name, address)` | Get tx params for updating the address record |
|
|
484
|
+
| `validateAvatarUri(uri)` | Validate avatar format and data URI size |
|
|
485
|
+
| `validateAvatarFull(uri)` | Async validation including remote file size |
|
|
486
|
+
| `getRemoteAvatarSize(url)` | Get file size of remote URL (helper for UI) |
|
|
296
487
|
|
|
297
488
|
### React Hooks (`@monadns/sdk/react`)
|
|
298
489
|
|
|
299
|
-
| Hook | Returns
|
|
300
|
-
| ------------------------------------- |
|
|
301
|
-
| `useMNSName(address)` | `{ name, loading, error }`
|
|
302
|
-
| `useMNSAddress(name)` | `{ address, loading, error }`
|
|
303
|
-
| `useMNSDisplay(address)` | `{ displayName, monName, loading }`
|
|
304
|
-
| `useMNSResolve()` | `{ resolve, address, name, loading, error }`
|
|
305
|
-
| `useMNSText(name, key)` | `{ value, loading, error }`
|
|
306
|
-
| `useMNSAvatar(name)` | `{ url, loading, error }`
|
|
307
|
-
| `
|
|
308
|
-
| `
|
|
309
|
-
| `
|
|
310
|
-
| `
|
|
490
|
+
| Hook | Returns | Description |
|
|
491
|
+
| ------------------------------------- | -------------------------------------------------------------------- | ----------------------------- |
|
|
492
|
+
| `useMNSName(address)` | `{ name, loading, error }` | Reverse resolution |
|
|
493
|
+
| `useMNSAddress(name)` | `{ address, loading, error }` | Forward resolution |
|
|
494
|
+
| `useMNSDisplay(address)` | `{ displayName, monName, loading }` | Display-ready name |
|
|
495
|
+
| `useMNSResolve()` | `{ resolve, address, name, loading, error }` | On-demand resolver for inputs |
|
|
496
|
+
| `useMNSText(name, key)` | `{ value, loading, error }` | Read text records |
|
|
497
|
+
| `useMNSAvatar(name)` | `{ url, loading, error }` | Resolved avatar URL |
|
|
498
|
+
| `useMNSOwner(name)` | `{ owner, loading, error }` | Get name owner |
|
|
499
|
+
| `useMNSResolver(name)` | `{ resolver, loading, error }` | Get resolver address |
|
|
500
|
+
| `useMNSExpiry(name)` | `{ expiry, expiryDate, daysUntilExpiry, isExpired, loading, error }` | Get expiration info |
|
|
501
|
+
| `useRegistrationInfo(label, address)` | `{ info, loading, error }` | Pre-registration data |
|
|
502
|
+
| `useMNSRegister()` | `{ prepare, tx, loading, error }` | Prepare registration tx |
|
|
503
|
+
| `useMNSTextRecords()` | `{ setAvatar, setAvatarValidated, setTwitter, ... }` | Prepare text record txs |
|
|
504
|
+
| `useMNSAddr()` | `{ setAddr }` | Prepare address update tx |
|
|
505
|
+
|
|
506
|
+
### Constants
|
|
507
|
+
|
|
508
|
+
| Constant | Value |
|
|
509
|
+
| ---------------------------- | -------------------------------------------- |
|
|
510
|
+
| `MAX_AVATAR_BYTES` | `51200` (50KB) |
|
|
511
|
+
| `DEFAULT_AVATAR_PLACEHOLDER` | Transparent 1x1 SVG data URI |
|
|
512
|
+
| `MNS_REGISTRY` | `0x13f963486e741c8d3fcdc0a34a910920339a19ce` |
|
|
513
|
+
| `MNS_PUBLIC_RESOLVER` | `0xa2eb94c88e55d944aced2066c5cec9b759801f97` |
|
|
514
|
+
| `MNS_CONTROLLER` | `0x98866c55adbc73ec6c272bb3604ddbdee3f282a8` |
|
|
515
|
+
| `MNS_BASE_REGISTRAR` | `0x104a49db9318c284d462841b6940bdb46624ca55` |
|
|
311
516
|
|
|
312
517
|
### Custom RPC
|
|
313
518
|
|
|
@@ -341,6 +546,49 @@ const name = await lookupAddress('0x...', { client });
|
|
|
341
546
|
|
|
342
547
|
**Chain:** Monad Mainnet (Chain ID 143)
|
|
343
548
|
|
|
549
|
+
## Migrating from v2.1 to v2.2
|
|
550
|
+
|
|
551
|
+
### New Features
|
|
552
|
+
|
|
553
|
+
- **Avatar validation**: `validateAvatarUri()` and `validateAvatarFull()` for size checking
|
|
554
|
+
- **Avatar constants**: `MAX_AVATAR_BYTES` (50KB limit) and `DEFAULT_AVATAR_PLACEHOLDER`
|
|
555
|
+
- **Enhanced avatar resolution**: Support for raw IPFS CIDs and Arweave URIs
|
|
556
|
+
- **Validated avatar setter**: `setAvatarValidated()` hook with automatic size validation
|
|
557
|
+
- **Size helper**: `getRemoteAvatarSize()` for checking remote file sizes
|
|
558
|
+
- **Fallback support**: `getAvatarUrl()` now accepts fallback option
|
|
559
|
+
|
|
560
|
+
### Behavior Changes
|
|
561
|
+
|
|
562
|
+
- Data URI avatars are now strictly limited to 50KB (throws error if exceeded)
|
|
563
|
+
- Setting non-data-URI avatars without validation logs a warning
|
|
564
|
+
- `getAvatarUrl()` now handles raw IPFS CIDs (e.g., `QmX...`) automatically
|
|
565
|
+
|
|
566
|
+
## Migrating from v2.0 to v2.1
|
|
567
|
+
|
|
568
|
+
### Breaking Changes
|
|
569
|
+
|
|
570
|
+
**`isRegistered()` has been renamed to `getAvailable()` with inverted logic**
|
|
571
|
+
|
|
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
|
+
```
|
|
580
|
+
|
|
581
|
+
**After (v2.1+):**
|
|
582
|
+
|
|
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
|
+
```
|
|
591
|
+
|
|
344
592
|
## Compatibility
|
|
345
593
|
|
|
346
594
|
- **Frameworks:** React 17+, Next.js (App Router & Pages), Remix, Vite, CRA
|
|
@@ -350,4 +598,4 @@ const name = await lookupAddress('0x...', { client });
|
|
|
350
598
|
|
|
351
599
|
## License
|
|
352
600
|
|
|
353
|
-
[Unlicense](https://unlicense.org)
|
|
601
|
+
[Unlicense](https://unlicense.org) – public domain, no attribution required.
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
DEFAULT_AVATAR_PLACEHOLDER: () => DEFAULT_AVATAR_PLACEHOLDER,
|
|
24
|
+
MAX_AVATAR_BYTES: () => MAX_AVATAR_BYTES,
|
|
25
|
+
MNS_BASE_REGISTRAR: () => MNS_BASE_REGISTRAR,
|
|
23
26
|
MNS_CONTROLLER: () => MNS_CONTROLLER,
|
|
24
27
|
MNS_PUBLIC_RESOLVER: () => MNS_PUBLIC_RESOLVER,
|
|
25
28
|
MNS_REGISTRY: () => MNS_REGISTRY,
|
|
@@ -27,19 +30,25 @@ __export(src_exports, {
|
|
|
27
30
|
clearCache: () => clearCache,
|
|
28
31
|
controllerReadAbi: () => controllerReadAbi,
|
|
29
32
|
controllerWriteAbi: () => controllerWriteAbi,
|
|
33
|
+
getAvailable: () => getAvailable,
|
|
30
34
|
getAvatarUrl: () => getAvatarUrl,
|
|
31
35
|
getDisplayName: () => getDisplayName,
|
|
36
|
+
getExpiry: () => getExpiry,
|
|
32
37
|
getMNSClient: () => getMNSClient,
|
|
38
|
+
getOwner: () => getOwner,
|
|
33
39
|
getRegisterTx: () => getRegisterTx,
|
|
34
40
|
getRegistrationInfo: () => getRegistrationInfo,
|
|
41
|
+
getRemoteAvatarSize: () => getRemoteAvatarSize,
|
|
42
|
+
getResolver: () => getResolver,
|
|
35
43
|
getSetAddrTx: () => getSetAddrTx,
|
|
36
44
|
getSetTextTx: () => getSetTextTx,
|
|
37
45
|
getTextRecord: () => getTextRecord,
|
|
38
|
-
isRegistered: () => isRegistered,
|
|
39
46
|
lookupAddress: () => lookupAddress,
|
|
40
47
|
resolveInput: () => resolveInput,
|
|
41
48
|
resolveName: () => resolveName,
|
|
42
|
-
resolverWriteAbi: () => resolverWriteAbi
|
|
49
|
+
resolverWriteAbi: () => resolverWriteAbi,
|
|
50
|
+
validateAvatarFull: () => validateAvatarFull,
|
|
51
|
+
validateAvatarUri: () => validateAvatarUri
|
|
43
52
|
});
|
|
44
53
|
module.exports = __toCommonJS(src_exports);
|
|
45
54
|
|
|
@@ -71,7 +80,10 @@ function getMNSClient(config) {
|
|
|
71
80
|
var MNS_REGISTRY = "0x13f963486e741c8d3fcdc0a34a910920339a19ce";
|
|
72
81
|
var MNS_PUBLIC_RESOLVER = "0xa2eb94c88e55d944aced2066c5cec9b759801f97";
|
|
73
82
|
var MNS_CONTROLLER = "0x98866c55adbc73ec6c272bb3604ddbdee3f282a8";
|
|
83
|
+
var MNS_BASE_REGISTRAR = "0x104a49db9318c284d462841b6940bdb46624ca55";
|
|
74
84
|
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
85
|
+
var MAX_AVATAR_BYTES = 51200;
|
|
86
|
+
var DEFAULT_AVATAR_PLACEHOLDER = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg"%3E%3C/svg%3E';
|
|
75
87
|
var registryAbi = [
|
|
76
88
|
{
|
|
77
89
|
name: "resolver",
|
|
@@ -79,6 +91,13 @@ var registryAbi = [
|
|
|
79
91
|
inputs: [{ name: "node", type: "bytes32" }],
|
|
80
92
|
outputs: [{ name: "", type: "address" }],
|
|
81
93
|
stateMutability: "view"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "owner",
|
|
97
|
+
type: "function",
|
|
98
|
+
inputs: [{ name: "node", type: "bytes32" }],
|
|
99
|
+
outputs: [{ name: "", type: "address" }],
|
|
100
|
+
stateMutability: "view"
|
|
82
101
|
}
|
|
83
102
|
];
|
|
84
103
|
var resolverAbi = [
|
|
@@ -203,6 +222,64 @@ async function getTextRecord(name, key, config) {
|
|
|
203
222
|
return null;
|
|
204
223
|
}
|
|
205
224
|
}
|
|
225
|
+
async function getOwner(name, config) {
|
|
226
|
+
try {
|
|
227
|
+
const client = getMNSClient(config);
|
|
228
|
+
const node = (0, import_viem2.namehash)(name.toLowerCase());
|
|
229
|
+
const owner = await client.readContract({
|
|
230
|
+
address: MNS_REGISTRY,
|
|
231
|
+
abi: registryAbi,
|
|
232
|
+
functionName: "owner",
|
|
233
|
+
args: [node]
|
|
234
|
+
});
|
|
235
|
+
return owner === ZERO_ADDRESS ? null : owner;
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async function getResolver(name, config) {
|
|
241
|
+
try {
|
|
242
|
+
const client = getMNSClient(config);
|
|
243
|
+
const node = (0, import_viem2.namehash)(name.toLowerCase());
|
|
244
|
+
const resolver = await client.readContract({
|
|
245
|
+
address: MNS_REGISTRY,
|
|
246
|
+
abi: registryAbi,
|
|
247
|
+
functionName: "resolver",
|
|
248
|
+
args: [node]
|
|
249
|
+
});
|
|
250
|
+
return resolver === ZERO_ADDRESS ? null : resolver;
|
|
251
|
+
} catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async function getExpiry(name, config) {
|
|
256
|
+
try {
|
|
257
|
+
const client = getMNSClient(config);
|
|
258
|
+
const label = name.replace(".mon", "");
|
|
259
|
+
const labelHash = (0, import_viem2.keccak256)((0, import_viem2.toBytes)(label));
|
|
260
|
+
const expiry = await client.readContract({
|
|
261
|
+
address: MNS_BASE_REGISTRAR,
|
|
262
|
+
abi: [
|
|
263
|
+
{
|
|
264
|
+
name: "nameExpires",
|
|
265
|
+
type: "function",
|
|
266
|
+
inputs: [{ name: "id", type: "uint256" }],
|
|
267
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
268
|
+
stateMutability: "view"
|
|
269
|
+
}
|
|
270
|
+
],
|
|
271
|
+
functionName: "nameExpires",
|
|
272
|
+
args: [BigInt(labelHash)]
|
|
273
|
+
});
|
|
274
|
+
return Number(expiry);
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function getAvailable(name, config) {
|
|
280
|
+
const addr = await resolveName(name, config);
|
|
281
|
+
return addr === null;
|
|
282
|
+
}
|
|
206
283
|
async function getDisplayName(address, config) {
|
|
207
284
|
const name = await lookupAddress(address, config);
|
|
208
285
|
if (name) return name;
|
|
@@ -215,25 +292,28 @@ async function resolveInput(input, config) {
|
|
|
215
292
|
if (trimmed.endsWith(".mon")) return resolveName(trimmed, config);
|
|
216
293
|
return resolveName(`${trimmed}.mon`, config);
|
|
217
294
|
}
|
|
218
|
-
async function
|
|
219
|
-
const addr = await resolveName(name, config);
|
|
220
|
-
return addr !== null;
|
|
221
|
-
}
|
|
222
|
-
async function getAvatarUrl(name, config) {
|
|
295
|
+
async function getAvatarUrl(name, config, options) {
|
|
223
296
|
const raw = await getTextRecord(name, "avatar", config);
|
|
224
|
-
if (!raw) return null;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
/^
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
297
|
+
if (!raw) return options?.fallback || null;
|
|
298
|
+
try {
|
|
299
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
300
|
+
return raw;
|
|
301
|
+
}
|
|
302
|
+
if (raw.startsWith("ipfs://")) {
|
|
303
|
+
const hash = raw.slice(7);
|
|
304
|
+
return `https://ipfs.io/ipfs/${hash}`;
|
|
305
|
+
}
|
|
306
|
+
if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(raw)) {
|
|
307
|
+
return `https://ipfs.io/ipfs/${raw}`;
|
|
308
|
+
}
|
|
309
|
+
if (raw.startsWith("ar://")) {
|
|
310
|
+
const hash = raw.slice(5);
|
|
311
|
+
return `https://arweave.net/${hash}`;
|
|
312
|
+
}
|
|
313
|
+
const nftMatch = raw.match(
|
|
314
|
+
/^eip155:(\d+)\/(erc721|erc1155):0x([a-fA-F0-9]{40})\/(\d+)$/
|
|
315
|
+
);
|
|
316
|
+
if (nftMatch) {
|
|
237
317
|
const client = getMNSClient(config);
|
|
238
318
|
const contract = `0x${nftMatch[3]}`;
|
|
239
319
|
const tokenId = BigInt(nftMatch[4]);
|
|
@@ -261,15 +341,16 @@ async function getAvatarUrl(name, config) {
|
|
|
261
341
|
if (image && image.startsWith("ipfs://")) {
|
|
262
342
|
image = `https://ipfs.io/ipfs/${image.slice(7)}`;
|
|
263
343
|
}
|
|
264
|
-
return image;
|
|
265
|
-
} catch {
|
|
266
|
-
return null;
|
|
344
|
+
return image || options?.fallback || null;
|
|
267
345
|
}
|
|
346
|
+
if (raw.startsWith("data:")) {
|
|
347
|
+
return raw;
|
|
348
|
+
}
|
|
349
|
+
return options?.fallback || null;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.warn("Failed to resolve avatar:", error);
|
|
352
|
+
return options?.fallback || null;
|
|
268
353
|
}
|
|
269
|
-
if (raw.startsWith("data:")) {
|
|
270
|
-
return raw;
|
|
271
|
-
}
|
|
272
|
-
return null;
|
|
273
354
|
}
|
|
274
355
|
function clearCache() {
|
|
275
356
|
nameCache.clear();
|
|
@@ -278,6 +359,102 @@ function clearCache() {
|
|
|
278
359
|
|
|
279
360
|
// src/write.ts
|
|
280
361
|
var import_viem3 = require("viem");
|
|
362
|
+
|
|
363
|
+
// src/utils.ts
|
|
364
|
+
function validateAvatarUri(uri) {
|
|
365
|
+
if (!uri) {
|
|
366
|
+
return { valid: false, error: "Avatar URI is required" };
|
|
367
|
+
}
|
|
368
|
+
if (uri.startsWith("data:")) {
|
|
369
|
+
const sizeBytes = new Blob([uri]).size;
|
|
370
|
+
if (sizeBytes > MAX_AVATAR_BYTES) {
|
|
371
|
+
return {
|
|
372
|
+
valid: false,
|
|
373
|
+
error: `Avatar size (${(sizeBytes / 1024).toFixed(1)}KB) exceeds ${MAX_AVATAR_BYTES / 1024}KB limit. Please optimize as WebP or SVG for better performance.`,
|
|
374
|
+
sizeBytes
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return { valid: true, sizeBytes };
|
|
378
|
+
}
|
|
379
|
+
const validSchemes = ["http://", "https://", "ipfs://", "eip155:", "ar://"];
|
|
380
|
+
const hasValidScheme = validSchemes.some((scheme) => uri.startsWith(scheme));
|
|
381
|
+
if (!hasValidScheme) {
|
|
382
|
+
if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(uri)) {
|
|
383
|
+
return { valid: true };
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
valid: false,
|
|
387
|
+
error: "Avatar must be a valid HTTP, IPFS, Arweave, or NFT URI"
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return { valid: true };
|
|
391
|
+
}
|
|
392
|
+
async function validateAvatarFull(uri, options) {
|
|
393
|
+
const maxBytes = options?.maxBytes || MAX_AVATAR_BYTES;
|
|
394
|
+
const timeout = options?.timeout || 5e3;
|
|
395
|
+
const formatValidation = validateAvatarUri(uri);
|
|
396
|
+
if (!formatValidation.valid) {
|
|
397
|
+
throw new Error(formatValidation.error);
|
|
398
|
+
}
|
|
399
|
+
if (uri.startsWith("data:")) {
|
|
400
|
+
return { valid: true, sizeBytes: formatValidation.sizeBytes };
|
|
401
|
+
}
|
|
402
|
+
if (uri.startsWith("eip155:")) {
|
|
403
|
+
return { valid: true };
|
|
404
|
+
}
|
|
405
|
+
let checkUrl = uri;
|
|
406
|
+
if (uri.startsWith("ipfs://")) {
|
|
407
|
+
checkUrl = `https://ipfs.io/ipfs/${uri.slice(7)}`;
|
|
408
|
+
} else if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(uri)) {
|
|
409
|
+
checkUrl = `https://ipfs.io/ipfs/${uri}`;
|
|
410
|
+
} else if (uri.startsWith("ar://")) {
|
|
411
|
+
checkUrl = `https://arweave.net/${uri.slice(5)}`;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const controller = new AbortController();
|
|
415
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
416
|
+
const response = await fetch(checkUrl, {
|
|
417
|
+
method: "HEAD",
|
|
418
|
+
signal: controller.signal
|
|
419
|
+
});
|
|
420
|
+
clearTimeout(timeoutId);
|
|
421
|
+
const contentLength = response.headers.get("content-length");
|
|
422
|
+
if (contentLength) {
|
|
423
|
+
const sizeBytes = parseInt(contentLength, 10);
|
|
424
|
+
if (sizeBytes > maxBytes) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`Avatar file size (${(sizeBytes / 1024).toFixed(1)}KB) exceeds ${maxBytes / 1024}KB limit. Please optimize as WebP or SVG for better performance.`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
return { valid: true, sizeBytes };
|
|
430
|
+
}
|
|
431
|
+
console.warn(
|
|
432
|
+
`Could not determine avatar size for ${uri}. Ensure it's under ${maxBytes / 1024}KB.`
|
|
433
|
+
);
|
|
434
|
+
return { valid: true };
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (error.name === "AbortError") {
|
|
437
|
+
console.warn(`Avatar size check timed out for ${uri}`);
|
|
438
|
+
return { valid: true };
|
|
439
|
+
}
|
|
440
|
+
if (error.message.includes("exceeds")) {
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
console.warn(`Could not check avatar size: ${error.message}`);
|
|
444
|
+
return { valid: true };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function getRemoteAvatarSize(url) {
|
|
448
|
+
try {
|
|
449
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
450
|
+
const contentLength = response.headers.get("content-length");
|
|
451
|
+
return contentLength ? parseInt(contentLength, 10) : null;
|
|
452
|
+
} catch {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/write.ts
|
|
281
458
|
var controllerReadAbi = [
|
|
282
459
|
{
|
|
283
460
|
name: "available",
|
|
@@ -394,6 +571,17 @@ async function getRegisterTx(label, ownerAddress, durationYears = 1, config) {
|
|
|
394
571
|
};
|
|
395
572
|
}
|
|
396
573
|
function getSetTextTx(name, key, value) {
|
|
574
|
+
if (key === "avatar" && value.startsWith("data:")) {
|
|
575
|
+
const validation = validateAvatarUri(value);
|
|
576
|
+
if (!validation.valid) {
|
|
577
|
+
throw new Error(validation.error);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (key === "avatar" && !value.startsWith("data:")) {
|
|
581
|
+
console.warn(
|
|
582
|
+
"\u26A0\uFE0F Avatar set without size validation. For best practices, use setAvatarValidated() or validateAvatarFull() before calling setAvatar()."
|
|
583
|
+
);
|
|
584
|
+
}
|
|
397
585
|
const node = (0, import_viem3.namehash)(
|
|
398
586
|
name.endsWith(".mon") ? name : `${name}.mon`
|
|
399
587
|
);
|
|
@@ -427,6 +615,9 @@ var TEXT_RECORD_KEYS = {
|
|
|
427
615
|
};
|
|
428
616
|
// Annotate the CommonJS export names for ESM import in node:
|
|
429
617
|
0 && (module.exports = {
|
|
618
|
+
DEFAULT_AVATAR_PLACEHOLDER,
|
|
619
|
+
MAX_AVATAR_BYTES,
|
|
620
|
+
MNS_BASE_REGISTRAR,
|
|
430
621
|
MNS_CONTROLLER,
|
|
431
622
|
MNS_PUBLIC_RESOLVER,
|
|
432
623
|
MNS_REGISTRY,
|
|
@@ -434,18 +625,24 @@ var TEXT_RECORD_KEYS = {
|
|
|
434
625
|
clearCache,
|
|
435
626
|
controllerReadAbi,
|
|
436
627
|
controllerWriteAbi,
|
|
628
|
+
getAvailable,
|
|
437
629
|
getAvatarUrl,
|
|
438
630
|
getDisplayName,
|
|
631
|
+
getExpiry,
|
|
439
632
|
getMNSClient,
|
|
633
|
+
getOwner,
|
|
440
634
|
getRegisterTx,
|
|
441
635
|
getRegistrationInfo,
|
|
636
|
+
getRemoteAvatarSize,
|
|
637
|
+
getResolver,
|
|
442
638
|
getSetAddrTx,
|
|
443
639
|
getSetTextTx,
|
|
444
640
|
getTextRecord,
|
|
445
|
-
isRegistered,
|
|
446
641
|
lookupAddress,
|
|
447
642
|
resolveInput,
|
|
448
643
|
resolveName,
|
|
449
|
-
resolverWriteAbi
|
|
644
|
+
resolverWriteAbi,
|
|
645
|
+
validateAvatarFull,
|
|
646
|
+
validateAvatarUri
|
|
450
647
|
});
|
|
451
648
|
//# sourceMappingURL=index.cjs.map
|