@monadns/sdk 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +266 -18
- package/dist/index.cjs +226 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -12
- package/dist/index.d.ts +133 -12
- package/dist/index.js +216 -28
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +278 -20
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +88 -1
- package/dist/react.d.ts +88 -1
- package/dist/react.js +276 -21
- package/dist/react.js.map +1 -1
- package/package.json +3 -3
package/dist/react.d.cts
CHANGED
|
@@ -204,6 +204,39 @@ declare function useMNSTextRecords(): {
|
|
|
204
204
|
functionName: "setText";
|
|
205
205
|
args: readonly [`0x${string}`, string, string];
|
|
206
206
|
};
|
|
207
|
+
setAvatarValidated: (name: string, url: string) => Promise<{
|
|
208
|
+
address: "0xa2eb94c88e55d944aced2066c5cec9b759801f97";
|
|
209
|
+
abi: readonly [{
|
|
210
|
+
readonly name: "setText";
|
|
211
|
+
readonly type: "function";
|
|
212
|
+
readonly inputs: readonly [{
|
|
213
|
+
readonly name: "node";
|
|
214
|
+
readonly type: "bytes32";
|
|
215
|
+
}, {
|
|
216
|
+
readonly name: "key";
|
|
217
|
+
readonly type: "string";
|
|
218
|
+
}, {
|
|
219
|
+
readonly name: "value";
|
|
220
|
+
readonly type: "string";
|
|
221
|
+
}];
|
|
222
|
+
readonly outputs: readonly [];
|
|
223
|
+
readonly stateMutability: "nonpayable";
|
|
224
|
+
}, {
|
|
225
|
+
readonly name: "setAddr";
|
|
226
|
+
readonly type: "function";
|
|
227
|
+
readonly inputs: readonly [{
|
|
228
|
+
readonly name: "node";
|
|
229
|
+
readonly type: "bytes32";
|
|
230
|
+
}, {
|
|
231
|
+
readonly name: "a";
|
|
232
|
+
readonly type: "address";
|
|
233
|
+
}];
|
|
234
|
+
readonly outputs: readonly [];
|
|
235
|
+
readonly stateMutability: "nonpayable";
|
|
236
|
+
}];
|
|
237
|
+
functionName: "setText";
|
|
238
|
+
args: readonly [`0x${string}`, string, string];
|
|
239
|
+
}>;
|
|
207
240
|
setUrl: (name: string, url: string) => {
|
|
208
241
|
address: "0xa2eb94c88e55d944aced2066c5cec9b759801f97";
|
|
209
242
|
abi: readonly [{
|
|
@@ -537,5 +570,59 @@ declare function useMNSAvatar(name: string | undefined, config?: MNSClientConfig
|
|
|
537
570
|
loading: boolean;
|
|
538
571
|
error: string | null;
|
|
539
572
|
};
|
|
573
|
+
/**
|
|
574
|
+
* Owner hook: get the owner address of a .mon name
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* ```tsx
|
|
578
|
+
* function OwnerInfo({ name }: { name: string }) {
|
|
579
|
+
* const { owner, loading } = useMNSOwner(name)
|
|
580
|
+
* if (loading) return <div>Loading...</div>
|
|
581
|
+
* return <div>Owner: {owner}</div>
|
|
582
|
+
* }
|
|
583
|
+
* ```
|
|
584
|
+
*/
|
|
585
|
+
declare function useMNSOwner(name: string | undefined, config?: MNSClientConfig): {
|
|
586
|
+
owner: string | null;
|
|
587
|
+
loading: boolean;
|
|
588
|
+
error: string | null;
|
|
589
|
+
};
|
|
590
|
+
/**
|
|
591
|
+
* Resolver hook: get the resolver contract address for a .mon name
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* ```tsx
|
|
595
|
+
* function ResolverInfo({ name }: { name: string }) {
|
|
596
|
+
* const { resolver, loading } = useMNSResolver(name)
|
|
597
|
+
* return <div>Resolver: {resolver}</div>
|
|
598
|
+
* }
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
declare function useMNSResolver(name: string | undefined, config?: MNSClientConfig): {
|
|
602
|
+
resolver: string | null;
|
|
603
|
+
loading: boolean;
|
|
604
|
+
error: string | null;
|
|
605
|
+
};
|
|
606
|
+
/**
|
|
607
|
+
* Expiry hook: get expiration info for a .mon name
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* ```tsx
|
|
611
|
+
* function ExpiryInfo({ name }: { name: string }) {
|
|
612
|
+
* const { expiry, expiryDate, daysUntilExpiry, isExpired, loading } = useMNSExpiry(name)
|
|
613
|
+
* if (loading) return <div>Loading...</div>
|
|
614
|
+
* if (isExpired) return <div className="text-red-500">Expired!</div>
|
|
615
|
+
* return <div>Expires in {daysUntilExpiry} days</div>
|
|
616
|
+
* }
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
declare function useMNSExpiry(name: string | undefined, config?: MNSClientConfig): {
|
|
620
|
+
expiry: number | null;
|
|
621
|
+
expiryDate: Date | null;
|
|
622
|
+
daysUntilExpiry: number | null;
|
|
623
|
+
isExpired: boolean | null;
|
|
624
|
+
loading: boolean;
|
|
625
|
+
error: string | null;
|
|
626
|
+
};
|
|
540
627
|
|
|
541
|
-
export { type MNSClientConfig, useMNSAddr, useMNSAddress, useMNSAvatar, useMNSDisplay, useMNSName, useMNSRegister, useMNSResolve, useMNSText, useMNSTextRecords, useRegistrationInfo };
|
|
628
|
+
export { type MNSClientConfig, useMNSAddr, useMNSAddress, useMNSAvatar, useMNSDisplay, useMNSExpiry, useMNSName, useMNSOwner, useMNSRegister, useMNSResolve, useMNSResolver, useMNSText, useMNSTextRecords, useRegistrationInfo };
|
package/dist/react.d.ts
CHANGED
|
@@ -204,6 +204,39 @@ declare function useMNSTextRecords(): {
|
|
|
204
204
|
functionName: "setText";
|
|
205
205
|
args: readonly [`0x${string}`, string, string];
|
|
206
206
|
};
|
|
207
|
+
setAvatarValidated: (name: string, url: string) => Promise<{
|
|
208
|
+
address: "0xa2eb94c88e55d944aced2066c5cec9b759801f97";
|
|
209
|
+
abi: readonly [{
|
|
210
|
+
readonly name: "setText";
|
|
211
|
+
readonly type: "function";
|
|
212
|
+
readonly inputs: readonly [{
|
|
213
|
+
readonly name: "node";
|
|
214
|
+
readonly type: "bytes32";
|
|
215
|
+
}, {
|
|
216
|
+
readonly name: "key";
|
|
217
|
+
readonly type: "string";
|
|
218
|
+
}, {
|
|
219
|
+
readonly name: "value";
|
|
220
|
+
readonly type: "string";
|
|
221
|
+
}];
|
|
222
|
+
readonly outputs: readonly [];
|
|
223
|
+
readonly stateMutability: "nonpayable";
|
|
224
|
+
}, {
|
|
225
|
+
readonly name: "setAddr";
|
|
226
|
+
readonly type: "function";
|
|
227
|
+
readonly inputs: readonly [{
|
|
228
|
+
readonly name: "node";
|
|
229
|
+
readonly type: "bytes32";
|
|
230
|
+
}, {
|
|
231
|
+
readonly name: "a";
|
|
232
|
+
readonly type: "address";
|
|
233
|
+
}];
|
|
234
|
+
readonly outputs: readonly [];
|
|
235
|
+
readonly stateMutability: "nonpayable";
|
|
236
|
+
}];
|
|
237
|
+
functionName: "setText";
|
|
238
|
+
args: readonly [`0x${string}`, string, string];
|
|
239
|
+
}>;
|
|
207
240
|
setUrl: (name: string, url: string) => {
|
|
208
241
|
address: "0xa2eb94c88e55d944aced2066c5cec9b759801f97";
|
|
209
242
|
abi: readonly [{
|
|
@@ -537,5 +570,59 @@ declare function useMNSAvatar(name: string | undefined, config?: MNSClientConfig
|
|
|
537
570
|
loading: boolean;
|
|
538
571
|
error: string | null;
|
|
539
572
|
};
|
|
573
|
+
/**
|
|
574
|
+
* Owner hook: get the owner address of a .mon name
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* ```tsx
|
|
578
|
+
* function OwnerInfo({ name }: { name: string }) {
|
|
579
|
+
* const { owner, loading } = useMNSOwner(name)
|
|
580
|
+
* if (loading) return <div>Loading...</div>
|
|
581
|
+
* return <div>Owner: {owner}</div>
|
|
582
|
+
* }
|
|
583
|
+
* ```
|
|
584
|
+
*/
|
|
585
|
+
declare function useMNSOwner(name: string | undefined, config?: MNSClientConfig): {
|
|
586
|
+
owner: string | null;
|
|
587
|
+
loading: boolean;
|
|
588
|
+
error: string | null;
|
|
589
|
+
};
|
|
590
|
+
/**
|
|
591
|
+
* Resolver hook: get the resolver contract address for a .mon name
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* ```tsx
|
|
595
|
+
* function ResolverInfo({ name }: { name: string }) {
|
|
596
|
+
* const { resolver, loading } = useMNSResolver(name)
|
|
597
|
+
* return <div>Resolver: {resolver}</div>
|
|
598
|
+
* }
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
declare function useMNSResolver(name: string | undefined, config?: MNSClientConfig): {
|
|
602
|
+
resolver: string | null;
|
|
603
|
+
loading: boolean;
|
|
604
|
+
error: string | null;
|
|
605
|
+
};
|
|
606
|
+
/**
|
|
607
|
+
* Expiry hook: get expiration info for a .mon name
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* ```tsx
|
|
611
|
+
* function ExpiryInfo({ name }: { name: string }) {
|
|
612
|
+
* const { expiry, expiryDate, daysUntilExpiry, isExpired, loading } = useMNSExpiry(name)
|
|
613
|
+
* if (loading) return <div>Loading...</div>
|
|
614
|
+
* if (isExpired) return <div className="text-red-500">Expired!</div>
|
|
615
|
+
* return <div>Expires in {daysUntilExpiry} days</div>
|
|
616
|
+
* }
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
declare function useMNSExpiry(name: string | undefined, config?: MNSClientConfig): {
|
|
620
|
+
expiry: number | null;
|
|
621
|
+
expiryDate: Date | null;
|
|
622
|
+
daysUntilExpiry: number | null;
|
|
623
|
+
isExpired: boolean | null;
|
|
624
|
+
loading: boolean;
|
|
625
|
+
error: string | null;
|
|
626
|
+
};
|
|
540
627
|
|
|
541
|
-
export { type MNSClientConfig, useMNSAddr, useMNSAddress, useMNSAvatar, useMNSDisplay, useMNSName, useMNSRegister, useMNSResolve, useMNSText, useMNSTextRecords, useRegistrationInfo };
|
|
628
|
+
export { type MNSClientConfig, useMNSAddr, useMNSAddress, useMNSAvatar, useMNSDisplay, useMNSExpiry, useMNSName, useMNSOwner, useMNSRegister, useMNSResolve, useMNSResolver, useMNSText, useMNSTextRecords, useRegistrationInfo };
|
package/dist/react.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
|
|
6
6
|
|
|
7
7
|
// src/resolve.ts
|
|
8
|
-
import { namehash } from "viem";
|
|
8
|
+
import { namehash, keccak256, toBytes } from "viem";
|
|
9
9
|
|
|
10
10
|
// src/client.ts
|
|
11
11
|
import {
|
|
@@ -35,7 +35,9 @@ function getMNSClient(config) {
|
|
|
35
35
|
var MNS_REGISTRY = "0x13f963486e741c8d3fcdc0a34a910920339a19ce";
|
|
36
36
|
var MNS_PUBLIC_RESOLVER = "0xa2eb94c88e55d944aced2066c5cec9b759801f97";
|
|
37
37
|
var MNS_CONTROLLER = "0x98866c55adbc73ec6c272bb3604ddbdee3f282a8";
|
|
38
|
+
var MNS_BASE_REGISTRAR = "0x104a49db9318c284d462841b6940bdb46624ca55";
|
|
38
39
|
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
40
|
+
var MAX_AVATAR_BYTES = 51200;
|
|
39
41
|
var registryAbi = [
|
|
40
42
|
{
|
|
41
43
|
name: "resolver",
|
|
@@ -43,6 +45,13 @@ var registryAbi = [
|
|
|
43
45
|
inputs: [{ name: "node", type: "bytes32" }],
|
|
44
46
|
outputs: [{ name: "", type: "address" }],
|
|
45
47
|
stateMutability: "view"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "owner",
|
|
51
|
+
type: "function",
|
|
52
|
+
inputs: [{ name: "node", type: "bytes32" }],
|
|
53
|
+
outputs: [{ name: "", type: "address" }],
|
|
54
|
+
stateMutability: "view"
|
|
46
55
|
}
|
|
47
56
|
];
|
|
48
57
|
var resolverAbi = [
|
|
@@ -167,21 +176,82 @@ async function getTextRecord(name, key, config) {
|
|
|
167
176
|
return null;
|
|
168
177
|
}
|
|
169
178
|
}
|
|
170
|
-
async function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
async function getOwner(name, config) {
|
|
180
|
+
try {
|
|
181
|
+
const client = getMNSClient(config);
|
|
182
|
+
const node = namehash(name.toLowerCase());
|
|
183
|
+
const owner = await client.readContract({
|
|
184
|
+
address: MNS_REGISTRY,
|
|
185
|
+
abi: registryAbi,
|
|
186
|
+
functionName: "owner",
|
|
187
|
+
args: [node]
|
|
188
|
+
});
|
|
189
|
+
return owner === ZERO_ADDRESS ? null : owner;
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
175
192
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
}
|
|
194
|
+
async function getResolver(name, config) {
|
|
195
|
+
try {
|
|
196
|
+
const client = getMNSClient(config);
|
|
197
|
+
const node = namehash(name.toLowerCase());
|
|
198
|
+
const resolver = await client.readContract({
|
|
199
|
+
address: MNS_REGISTRY,
|
|
200
|
+
abi: registryAbi,
|
|
201
|
+
functionName: "resolver",
|
|
202
|
+
args: [node]
|
|
203
|
+
});
|
|
204
|
+
return resolver === ZERO_ADDRESS ? null : resolver;
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
179
207
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
}
|
|
209
|
+
async function getExpiry(name, config) {
|
|
210
|
+
try {
|
|
211
|
+
const client = getMNSClient(config);
|
|
212
|
+
const label = name.replace(".mon", "");
|
|
213
|
+
const labelHash = keccak256(toBytes(label));
|
|
214
|
+
const expiry = await client.readContract({
|
|
215
|
+
address: MNS_BASE_REGISTRAR,
|
|
216
|
+
abi: [
|
|
217
|
+
{
|
|
218
|
+
name: "nameExpires",
|
|
219
|
+
type: "function",
|
|
220
|
+
inputs: [{ name: "id", type: "uint256" }],
|
|
221
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
222
|
+
stateMutability: "view"
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
functionName: "nameExpires",
|
|
226
|
+
args: [BigInt(labelHash)]
|
|
227
|
+
});
|
|
228
|
+
return Number(expiry);
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function getAvatarUrl(name, config, options) {
|
|
234
|
+
const raw = await getTextRecord(name, "avatar", config);
|
|
235
|
+
if (!raw) return options?.fallback || null;
|
|
236
|
+
try {
|
|
237
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
238
|
+
return raw;
|
|
239
|
+
}
|
|
240
|
+
if (raw.startsWith("ipfs://")) {
|
|
241
|
+
const hash = raw.slice(7);
|
|
242
|
+
return `https://ipfs.io/ipfs/${hash}`;
|
|
243
|
+
}
|
|
244
|
+
if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(raw)) {
|
|
245
|
+
return `https://ipfs.io/ipfs/${raw}`;
|
|
246
|
+
}
|
|
247
|
+
if (raw.startsWith("ar://")) {
|
|
248
|
+
const hash = raw.slice(5);
|
|
249
|
+
return `https://arweave.net/${hash}`;
|
|
250
|
+
}
|
|
251
|
+
const nftMatch = raw.match(
|
|
252
|
+
/^eip155:(\d+)\/(erc721|erc1155):0x([a-fA-F0-9]{40})\/(\d+)$/
|
|
253
|
+
);
|
|
254
|
+
if (nftMatch) {
|
|
185
255
|
const client = getMNSClient(config);
|
|
186
256
|
const contract = `0x${nftMatch[3]}`;
|
|
187
257
|
const tokenId = BigInt(nftMatch[4]);
|
|
@@ -209,15 +279,16 @@ async function getAvatarUrl(name, config) {
|
|
|
209
279
|
if (image && image.startsWith("ipfs://")) {
|
|
210
280
|
image = `https://ipfs.io/ipfs/${image.slice(7)}`;
|
|
211
281
|
}
|
|
212
|
-
return image;
|
|
213
|
-
} catch {
|
|
214
|
-
return null;
|
|
282
|
+
return image || options?.fallback || null;
|
|
215
283
|
}
|
|
284
|
+
if (raw.startsWith("data:")) {
|
|
285
|
+
return raw;
|
|
286
|
+
}
|
|
287
|
+
return options?.fallback || null;
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.warn("Failed to resolve avatar:", error);
|
|
290
|
+
return options?.fallback || null;
|
|
216
291
|
}
|
|
217
|
-
if (raw.startsWith("data:")) {
|
|
218
|
-
return raw;
|
|
219
|
-
}
|
|
220
|
-
return null;
|
|
221
292
|
}
|
|
222
293
|
|
|
223
294
|
// src/react-write.ts
|
|
@@ -225,6 +296,93 @@ import { useState, useEffect, useCallback } from "react";
|
|
|
225
296
|
|
|
226
297
|
// src/write.ts
|
|
227
298
|
import { namehash as namehash2 } from "viem";
|
|
299
|
+
|
|
300
|
+
// src/utils.ts
|
|
301
|
+
function validateAvatarUri(uri) {
|
|
302
|
+
if (!uri) {
|
|
303
|
+
return { valid: false, error: "Avatar URI is required" };
|
|
304
|
+
}
|
|
305
|
+
if (uri.startsWith("data:")) {
|
|
306
|
+
const sizeBytes = new Blob([uri]).size;
|
|
307
|
+
if (sizeBytes > MAX_AVATAR_BYTES) {
|
|
308
|
+
return {
|
|
309
|
+
valid: false,
|
|
310
|
+
error: `Avatar size (${(sizeBytes / 1024).toFixed(1)}KB) exceeds ${MAX_AVATAR_BYTES / 1024}KB limit. Please optimize as WebP or SVG for better performance.`,
|
|
311
|
+
sizeBytes
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return { valid: true, sizeBytes };
|
|
315
|
+
}
|
|
316
|
+
const validSchemes = ["http://", "https://", "ipfs://", "eip155:", "ar://"];
|
|
317
|
+
const hasValidScheme = validSchemes.some((scheme) => uri.startsWith(scheme));
|
|
318
|
+
if (!hasValidScheme) {
|
|
319
|
+
if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(uri)) {
|
|
320
|
+
return { valid: true };
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
valid: false,
|
|
324
|
+
error: "Avatar must be a valid HTTP, IPFS, Arweave, or NFT URI"
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return { valid: true };
|
|
328
|
+
}
|
|
329
|
+
async function validateAvatarFull(uri, options) {
|
|
330
|
+
const maxBytes = options?.maxBytes || MAX_AVATAR_BYTES;
|
|
331
|
+
const timeout = options?.timeout || 5e3;
|
|
332
|
+
const formatValidation = validateAvatarUri(uri);
|
|
333
|
+
if (!formatValidation.valid) {
|
|
334
|
+
throw new Error(formatValidation.error);
|
|
335
|
+
}
|
|
336
|
+
if (uri.startsWith("data:")) {
|
|
337
|
+
return { valid: true, sizeBytes: formatValidation.sizeBytes };
|
|
338
|
+
}
|
|
339
|
+
if (uri.startsWith("eip155:")) {
|
|
340
|
+
return { valid: true };
|
|
341
|
+
}
|
|
342
|
+
let checkUrl = uri;
|
|
343
|
+
if (uri.startsWith("ipfs://")) {
|
|
344
|
+
checkUrl = `https://ipfs.io/ipfs/${uri.slice(7)}`;
|
|
345
|
+
} else if (/^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[0-9A-Za-z]{50,})/.test(uri)) {
|
|
346
|
+
checkUrl = `https://ipfs.io/ipfs/${uri}`;
|
|
347
|
+
} else if (uri.startsWith("ar://")) {
|
|
348
|
+
checkUrl = `https://arweave.net/${uri.slice(5)}`;
|
|
349
|
+
}
|
|
350
|
+
try {
|
|
351
|
+
const controller = new AbortController();
|
|
352
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
353
|
+
const response = await fetch(checkUrl, {
|
|
354
|
+
method: "HEAD",
|
|
355
|
+
signal: controller.signal
|
|
356
|
+
});
|
|
357
|
+
clearTimeout(timeoutId);
|
|
358
|
+
const contentLength = response.headers.get("content-length");
|
|
359
|
+
if (contentLength) {
|
|
360
|
+
const sizeBytes = parseInt(contentLength, 10);
|
|
361
|
+
if (sizeBytes > maxBytes) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
`Avatar file size (${(sizeBytes / 1024).toFixed(1)}KB) exceeds ${maxBytes / 1024}KB limit. Please optimize as WebP or SVG for better performance.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
return { valid: true, sizeBytes };
|
|
367
|
+
}
|
|
368
|
+
console.warn(
|
|
369
|
+
`Could not determine avatar size for ${uri}. Ensure it's under ${maxBytes / 1024}KB.`
|
|
370
|
+
);
|
|
371
|
+
return { valid: true };
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (error.name === "AbortError") {
|
|
374
|
+
console.warn(`Avatar size check timed out for ${uri}`);
|
|
375
|
+
return { valid: true };
|
|
376
|
+
}
|
|
377
|
+
if (error.message.includes("exceeds")) {
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
console.warn(`Could not check avatar size: ${error.message}`);
|
|
381
|
+
return { valid: true };
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/write.ts
|
|
228
386
|
var controllerReadAbi = [
|
|
229
387
|
{
|
|
230
388
|
name: "available",
|
|
@@ -341,6 +499,17 @@ async function getRegisterTx(label, ownerAddress, durationYears = 1, config) {
|
|
|
341
499
|
};
|
|
342
500
|
}
|
|
343
501
|
function getSetTextTx(name, key, value) {
|
|
502
|
+
if (key === "avatar" && value.startsWith("data:")) {
|
|
503
|
+
const validation = validateAvatarUri(value);
|
|
504
|
+
if (!validation.valid) {
|
|
505
|
+
throw new Error(validation.error);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (key === "avatar" && !value.startsWith("data:")) {
|
|
509
|
+
console.warn(
|
|
510
|
+
"\u26A0\uFE0F Avatar set without size validation. For best practices, use setAvatarValidated() or validateAvatarFull() before calling setAvatar()."
|
|
511
|
+
);
|
|
512
|
+
}
|
|
344
513
|
const node = namehash2(
|
|
345
514
|
name.endsWith(".mon") ? name : `${name}.mon`
|
|
346
515
|
);
|
|
@@ -444,6 +613,10 @@ function useMNSTextRecords() {
|
|
|
444
613
|
const setAvatar = useCallback((name, url) => {
|
|
445
614
|
return getSetTextTx(name, "avatar", url);
|
|
446
615
|
}, []);
|
|
616
|
+
const setAvatarValidated = useCallback(async (name, url) => {
|
|
617
|
+
await validateAvatarFull(url);
|
|
618
|
+
return getSetTextTx(name, "avatar", url);
|
|
619
|
+
}, []);
|
|
447
620
|
const setUrl = useCallback((name, url) => {
|
|
448
621
|
return getSetTextTx(name, "url", url);
|
|
449
622
|
}, []);
|
|
@@ -462,6 +635,7 @@ function useMNSTextRecords() {
|
|
|
462
635
|
return {
|
|
463
636
|
setTextRecord,
|
|
464
637
|
setAvatar,
|
|
638
|
+
setAvatarValidated,
|
|
465
639
|
setUrl,
|
|
466
640
|
setTwitter,
|
|
467
641
|
setGithub,
|
|
@@ -626,14 +800,95 @@ function useMNSAvatar(name, config) {
|
|
|
626
800
|
}, [name]);
|
|
627
801
|
return { url, loading, error };
|
|
628
802
|
}
|
|
803
|
+
function useMNSOwner(name, config) {
|
|
804
|
+
const [owner, setOwner] = useState2(null);
|
|
805
|
+
const [loading, setLoading] = useState2(false);
|
|
806
|
+
const [error, setError] = useState2(null);
|
|
807
|
+
useEffect2(() => {
|
|
808
|
+
if (!name) {
|
|
809
|
+
setOwner(null);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
let cancelled = false;
|
|
813
|
+
setLoading(true);
|
|
814
|
+
setError(null);
|
|
815
|
+
getOwner(name, config).then((o) => {
|
|
816
|
+
if (!cancelled) setOwner(o);
|
|
817
|
+
}).catch((e) => {
|
|
818
|
+
if (!cancelled) setError(e.message);
|
|
819
|
+
}).finally(() => {
|
|
820
|
+
if (!cancelled) setLoading(false);
|
|
821
|
+
});
|
|
822
|
+
return () => {
|
|
823
|
+
cancelled = true;
|
|
824
|
+
};
|
|
825
|
+
}, [name]);
|
|
826
|
+
return { owner, loading, error };
|
|
827
|
+
}
|
|
828
|
+
function useMNSResolver(name, config) {
|
|
829
|
+
const [resolver, setResolver] = useState2(null);
|
|
830
|
+
const [loading, setLoading] = useState2(false);
|
|
831
|
+
const [error, setError] = useState2(null);
|
|
832
|
+
useEffect2(() => {
|
|
833
|
+
if (!name) {
|
|
834
|
+
setResolver(null);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
let cancelled = false;
|
|
838
|
+
setLoading(true);
|
|
839
|
+
setError(null);
|
|
840
|
+
getResolver(name, config).then((r) => {
|
|
841
|
+
if (!cancelled) setResolver(r);
|
|
842
|
+
}).catch((e) => {
|
|
843
|
+
if (!cancelled) setError(e.message);
|
|
844
|
+
}).finally(() => {
|
|
845
|
+
if (!cancelled) setLoading(false);
|
|
846
|
+
});
|
|
847
|
+
return () => {
|
|
848
|
+
cancelled = true;
|
|
849
|
+
};
|
|
850
|
+
}, [name]);
|
|
851
|
+
return { resolver, loading, error };
|
|
852
|
+
}
|
|
853
|
+
function useMNSExpiry(name, config) {
|
|
854
|
+
const [expiry, setExpiry] = useState2(null);
|
|
855
|
+
const [loading, setLoading] = useState2(false);
|
|
856
|
+
const [error, setError] = useState2(null);
|
|
857
|
+
useEffect2(() => {
|
|
858
|
+
if (!name) {
|
|
859
|
+
setExpiry(null);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
let cancelled = false;
|
|
863
|
+
setLoading(true);
|
|
864
|
+
setError(null);
|
|
865
|
+
getExpiry(name, config).then((e) => {
|
|
866
|
+
if (!cancelled) setExpiry(e);
|
|
867
|
+
}).catch((e) => {
|
|
868
|
+
if (!cancelled) setError(e.message);
|
|
869
|
+
}).finally(() => {
|
|
870
|
+
if (!cancelled) setLoading(false);
|
|
871
|
+
});
|
|
872
|
+
return () => {
|
|
873
|
+
cancelled = true;
|
|
874
|
+
};
|
|
875
|
+
}, [name]);
|
|
876
|
+
const expiryDate = expiry ? new Date(expiry * 1e3) : null;
|
|
877
|
+
const daysUntilExpiry = expiry ? Math.floor((expiry * 1e3 - Date.now()) / (1e3 * 60 * 60 * 24)) : null;
|
|
878
|
+
const isExpired = expiry ? expiry * 1e3 < Date.now() : null;
|
|
879
|
+
return { expiry, expiryDate, daysUntilExpiry, isExpired, loading, error };
|
|
880
|
+
}
|
|
629
881
|
export {
|
|
630
882
|
useMNSAddr,
|
|
631
883
|
useMNSAddress,
|
|
632
884
|
useMNSAvatar,
|
|
633
885
|
useMNSDisplay,
|
|
886
|
+
useMNSExpiry,
|
|
634
887
|
useMNSName,
|
|
888
|
+
useMNSOwner,
|
|
635
889
|
useMNSRegister,
|
|
636
890
|
useMNSResolve,
|
|
891
|
+
useMNSResolver,
|
|
637
892
|
useMNSText,
|
|
638
893
|
useMNSTextRecords,
|
|
639
894
|
useRegistrationInfo
|