@monadns/sdk 2.1.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 +157 -16
- package/dist/index.cjs +150 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -3
- package/dist/index.d.ts +71 -3
- package/dist/index.js +144 -22
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +133 -21
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +33 -0
- package/dist/react.d.ts +33 -0
- package/dist/react.js +133 -21
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
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
|
|
|
@@ -303,6 +303,126 @@ function ProfileEditor({ name }: { name: string }) {
|
|
|
303
303
|
}
|
|
304
304
|
```
|
|
305
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
|
+
|
|
306
426
|
## Advanced Usage
|
|
307
427
|
|
|
308
428
|
### Check Name Ownership & Expiry
|
|
@@ -351,7 +471,7 @@ if (available) {
|
|
|
351
471
|
| `getDisplayName(address)` | Returns `.mon` name or truncated address |
|
|
352
472
|
| `resolveInput(input)` | Accepts name or address, returns address |
|
|
353
473
|
| `getTextRecord(name, key)` | Get text record (avatar, url, etc.) |
|
|
354
|
-
| `getAvatarUrl(name)`
|
|
474
|
+
| `getAvatarUrl(name, config, options)` | Get resolved avatar URL (handles IPFS, NFTs) |
|
|
355
475
|
| `getAvailable(name)` | Check if a name is available for registration |
|
|
356
476
|
| `getOwner(name)` | Get the owner address of a name |
|
|
357
477
|
| `getResolver(name)` | Get the resolver contract address |
|
|
@@ -361,6 +481,9 @@ if (available) {
|
|
|
361
481
|
| `getRegisterTx(label, address)` | Get tx params for registration |
|
|
362
482
|
| `getSetTextTx(name, key, value)` | Get tx params for setting a text record |
|
|
363
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) |
|
|
364
487
|
|
|
365
488
|
### React Hooks (`@monadns/sdk/react`)
|
|
366
489
|
|
|
@@ -377,9 +500,20 @@ if (available) {
|
|
|
377
500
|
| `useMNSExpiry(name)` | `{ expiry, expiryDate, daysUntilExpiry, isExpired, loading, error }` | Get expiration info |
|
|
378
501
|
| `useRegistrationInfo(label, address)` | `{ info, loading, error }` | Pre-registration data |
|
|
379
502
|
| `useMNSRegister()` | `{ prepare, tx, loading, error }` | Prepare registration tx |
|
|
380
|
-
| `useMNSTextRecords()` | `{ setAvatar, setTwitter, ... }`
|
|
503
|
+
| `useMNSTextRecords()` | `{ setAvatar, setAvatarValidated, setTwitter, ... }` | Prepare text record txs |
|
|
381
504
|
| `useMNSAddr()` | `{ setAddr }` | Prepare address update tx |
|
|
382
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` |
|
|
516
|
+
|
|
383
517
|
### Custom RPC
|
|
384
518
|
|
|
385
519
|
All functions accept an optional config object:
|
|
@@ -412,6 +546,23 @@ const name = await lookupAddress('0x...', { client });
|
|
|
412
546
|
|
|
413
547
|
**Chain:** Monad Mainnet (Chain ID 143)
|
|
414
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
|
+
|
|
415
566
|
## Migrating from v2.0 to v2.1
|
|
416
567
|
|
|
417
568
|
### Breaking Changes
|
|
@@ -427,7 +578,7 @@ if (registered) {
|
|
|
427
578
|
}
|
|
428
579
|
```
|
|
429
580
|
|
|
430
|
-
**After (v2.1):**
|
|
581
|
+
**After (v2.1+):**
|
|
431
582
|
|
|
432
583
|
```typescript
|
|
433
584
|
const available = await getAvailable('alice.mon');
|
|
@@ -438,16 +589,6 @@ if (available) {
|
|
|
438
589
|
}
|
|
439
590
|
```
|
|
440
591
|
|
|
441
|
-
### New Features
|
|
442
|
-
|
|
443
|
-
- `getOwner(name)` - Get the owner of a name
|
|
444
|
-
- `getResolver(name)` - Get the resolver contract
|
|
445
|
-
- `getExpiry(name)` - Get expiration timestamp
|
|
446
|
-
- `useMNSOwner(name)` - React hook for ownership info
|
|
447
|
-
- `useMNSResolver(name)` - React hook for resolver info
|
|
448
|
-
- `useMNSExpiry(name)` - React hook for expiry info with computed fields
|
|
449
|
-
- `MNS_BASE_REGISTRAR` constant now exported
|
|
450
|
-
|
|
451
592
|
## Compatibility
|
|
452
593
|
|
|
453
594
|
- **Frameworks:** React 17+, Next.js (App Router & Pages), Remix, Vite, CRA
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,8 @@ 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,
|
|
23
25
|
MNS_BASE_REGISTRAR: () => MNS_BASE_REGISTRAR,
|
|
24
26
|
MNS_CONTROLLER: () => MNS_CONTROLLER,
|
|
25
27
|
MNS_PUBLIC_RESOLVER: () => MNS_PUBLIC_RESOLVER,
|
|
@@ -36,6 +38,7 @@ __export(src_exports, {
|
|
|
36
38
|
getOwner: () => getOwner,
|
|
37
39
|
getRegisterTx: () => getRegisterTx,
|
|
38
40
|
getRegistrationInfo: () => getRegistrationInfo,
|
|
41
|
+
getRemoteAvatarSize: () => getRemoteAvatarSize,
|
|
39
42
|
getResolver: () => getResolver,
|
|
40
43
|
getSetAddrTx: () => getSetAddrTx,
|
|
41
44
|
getSetTextTx: () => getSetTextTx,
|
|
@@ -43,7 +46,9 @@ __export(src_exports, {
|
|
|
43
46
|
lookupAddress: () => lookupAddress,
|
|
44
47
|
resolveInput: () => resolveInput,
|
|
45
48
|
resolveName: () => resolveName,
|
|
46
|
-
resolverWriteAbi: () => resolverWriteAbi
|
|
49
|
+
resolverWriteAbi: () => resolverWriteAbi,
|
|
50
|
+
validateAvatarFull: () => validateAvatarFull,
|
|
51
|
+
validateAvatarUri: () => validateAvatarUri
|
|
47
52
|
});
|
|
48
53
|
module.exports = __toCommonJS(src_exports);
|
|
49
54
|
|
|
@@ -77,6 +82,8 @@ var MNS_PUBLIC_RESOLVER = "0xa2eb94c88e55d944aced2066c5cec9b759801f97";
|
|
|
77
82
|
var MNS_CONTROLLER = "0x98866c55adbc73ec6c272bb3604ddbdee3f282a8";
|
|
78
83
|
var MNS_BASE_REGISTRAR = "0x104a49db9318c284d462841b6940bdb46624ca55";
|
|
79
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';
|
|
80
87
|
var registryAbi = [
|
|
81
88
|
{
|
|
82
89
|
name: "resolver",
|
|
@@ -285,21 +292,28 @@ async function resolveInput(input, config) {
|
|
|
285
292
|
if (trimmed.endsWith(".mon")) return resolveName(trimmed, config);
|
|
286
293
|
return resolveName(`${trimmed}.mon`, config);
|
|
287
294
|
}
|
|
288
|
-
async function getAvatarUrl(name, config) {
|
|
295
|
+
async function getAvatarUrl(name, config, options) {
|
|
289
296
|
const raw = await getTextRecord(name, "avatar", config);
|
|
290
|
-
if (!raw) return null;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
/^
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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) {
|
|
303
317
|
const client = getMNSClient(config);
|
|
304
318
|
const contract = `0x${nftMatch[3]}`;
|
|
305
319
|
const tokenId = BigInt(nftMatch[4]);
|
|
@@ -327,15 +341,16 @@ async function getAvatarUrl(name, config) {
|
|
|
327
341
|
if (image && image.startsWith("ipfs://")) {
|
|
328
342
|
image = `https://ipfs.io/ipfs/${image.slice(7)}`;
|
|
329
343
|
}
|
|
330
|
-
return image;
|
|
331
|
-
} catch {
|
|
332
|
-
return null;
|
|
344
|
+
return image || options?.fallback || null;
|
|
333
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;
|
|
334
353
|
}
|
|
335
|
-
if (raw.startsWith("data:")) {
|
|
336
|
-
return raw;
|
|
337
|
-
}
|
|
338
|
-
return null;
|
|
339
354
|
}
|
|
340
355
|
function clearCache() {
|
|
341
356
|
nameCache.clear();
|
|
@@ -344,6 +359,102 @@ function clearCache() {
|
|
|
344
359
|
|
|
345
360
|
// src/write.ts
|
|
346
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
|
|
347
458
|
var controllerReadAbi = [
|
|
348
459
|
{
|
|
349
460
|
name: "available",
|
|
@@ -460,6 +571,17 @@ async function getRegisterTx(label, ownerAddress, durationYears = 1, config) {
|
|
|
460
571
|
};
|
|
461
572
|
}
|
|
462
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
|
+
}
|
|
463
585
|
const node = (0, import_viem3.namehash)(
|
|
464
586
|
name.endsWith(".mon") ? name : `${name}.mon`
|
|
465
587
|
);
|
|
@@ -493,6 +615,8 @@ var TEXT_RECORD_KEYS = {
|
|
|
493
615
|
};
|
|
494
616
|
// Annotate the CommonJS export names for ESM import in node:
|
|
495
617
|
0 && (module.exports = {
|
|
618
|
+
DEFAULT_AVATAR_PLACEHOLDER,
|
|
619
|
+
MAX_AVATAR_BYTES,
|
|
496
620
|
MNS_BASE_REGISTRAR,
|
|
497
621
|
MNS_CONTROLLER,
|
|
498
622
|
MNS_PUBLIC_RESOLVER,
|
|
@@ -509,6 +633,7 @@ var TEXT_RECORD_KEYS = {
|
|
|
509
633
|
getOwner,
|
|
510
634
|
getRegisterTx,
|
|
511
635
|
getRegistrationInfo,
|
|
636
|
+
getRemoteAvatarSize,
|
|
512
637
|
getResolver,
|
|
513
638
|
getSetAddrTx,
|
|
514
639
|
getSetTextTx,
|
|
@@ -516,6 +641,8 @@ var TEXT_RECORD_KEYS = {
|
|
|
516
641
|
lookupAddress,
|
|
517
642
|
resolveInput,
|
|
518
643
|
resolveName,
|
|
519
|
-
resolverWriteAbi
|
|
644
|
+
resolverWriteAbi,
|
|
645
|
+
validateAvatarFull,
|
|
646
|
+
validateAvatarUri
|
|
520
647
|
});
|
|
521
648
|
//# sourceMappingURL=index.cjs.map
|