@monadns/sdk 1.0.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.
@@ -0,0 +1,109 @@
1
+ import { PublicClient, Transport, Chain } from 'viem';
2
+
3
+ type MNSClientConfig = {
4
+ /** Custom RPC URL. Defaults to the public Monad RPC. */
5
+ rpcUrl?: string;
6
+ /** Existing viem PublicClient to reuse instead of creating a new one. */
7
+ client?: PublicClient<Transport, Chain>;
8
+ };
9
+
10
+ /**
11
+ * Reverse resolution hook: address → .mon name
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function Profile({ address }: { address: string }) {
16
+ * const { name, loading } = useMNSName(address)
17
+ * return <span>{loading ? '...' : name ?? address}</span>
18
+ * }
19
+ * ```
20
+ */
21
+ declare function useMNSName(address: string | undefined, config?: MNSClientConfig): {
22
+ name: string | null;
23
+ loading: boolean;
24
+ error: string | null;
25
+ };
26
+ /**
27
+ * Forward resolution hook: .mon name → address
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * function Lookup() {
32
+ * const { address, loading } = useMNSAddress('alice.mon')
33
+ * return <span>{loading ? 'Resolving...' : address}</span>
34
+ * }
35
+ * ```
36
+ */
37
+ declare function useMNSAddress(name: string | undefined, config?: MNSClientConfig): {
38
+ address: string | null;
39
+ loading: boolean;
40
+ error: string | null;
41
+ };
42
+ /**
43
+ * Display name hook: .mon name if available, otherwise truncated address
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * function Badge({ address }: { address: string }) {
48
+ * const { displayName, monName } = useMNSDisplay(address)
49
+ * return (
50
+ * <span className={monName ? 'text-purple-400' : 'text-gray-500'}>
51
+ * {displayName}
52
+ * </span>
53
+ * )
54
+ * }
55
+ * ```
56
+ */
57
+ declare function useMNSDisplay(address: string | undefined, config?: MNSClientConfig): {
58
+ displayName: string;
59
+ monName: string | null;
60
+ loading: boolean;
61
+ error: string | null;
62
+ };
63
+ /**
64
+ * On-demand resolver hook: for input fields accepting names or addresses
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * function SendForm() {
69
+ * const { resolve, address, name, loading, error } = useMNSResolve()
70
+ *
71
+ * return (
72
+ * <div>
73
+ * <input
74
+ * placeholder="Address or .mon name"
75
+ * onChange={(e) => resolve(e.target.value)}
76
+ * />
77
+ * {loading && <span>Resolving...</span>}
78
+ * {error && <span className="text-red-500">{error}</span>}
79
+ * {address && <span className="text-green-500">→ {address}</span>}
80
+ * </div>
81
+ * )
82
+ * }
83
+ * ```
84
+ */
85
+ declare function useMNSResolve(config?: MNSClientConfig): {
86
+ resolve: (input: string) => Promise<void>;
87
+ address: string | null;
88
+ name: string | null;
89
+ loading: boolean;
90
+ error: string | null;
91
+ };
92
+ /**
93
+ * Text record hook: get metadata like avatar, URL, twitter handle
94
+ *
95
+ * @example
96
+ * ```tsx
97
+ * function Avatar({ name }: { name: string }) {
98
+ * const { value } = useMNSText(name, 'avatar')
99
+ * return value ? <img src={value} /> : null
100
+ * }
101
+ * ```
102
+ */
103
+ declare function useMNSText(name: string | undefined, key: string, config?: MNSClientConfig): {
104
+ value: string | null;
105
+ loading: boolean;
106
+ error: string | null;
107
+ };
108
+
109
+ export { type MNSClientConfig, useMNSAddress, useMNSDisplay, useMNSName, useMNSResolve, useMNSText };
@@ -0,0 +1,109 @@
1
+ import { PublicClient, Transport, Chain } from 'viem';
2
+
3
+ type MNSClientConfig = {
4
+ /** Custom RPC URL. Defaults to the public Monad RPC. */
5
+ rpcUrl?: string;
6
+ /** Existing viem PublicClient to reuse instead of creating a new one. */
7
+ client?: PublicClient<Transport, Chain>;
8
+ };
9
+
10
+ /**
11
+ * Reverse resolution hook: address → .mon name
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function Profile({ address }: { address: string }) {
16
+ * const { name, loading } = useMNSName(address)
17
+ * return <span>{loading ? '...' : name ?? address}</span>
18
+ * }
19
+ * ```
20
+ */
21
+ declare function useMNSName(address: string | undefined, config?: MNSClientConfig): {
22
+ name: string | null;
23
+ loading: boolean;
24
+ error: string | null;
25
+ };
26
+ /**
27
+ * Forward resolution hook: .mon name → address
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * function Lookup() {
32
+ * const { address, loading } = useMNSAddress('alice.mon')
33
+ * return <span>{loading ? 'Resolving...' : address}</span>
34
+ * }
35
+ * ```
36
+ */
37
+ declare function useMNSAddress(name: string | undefined, config?: MNSClientConfig): {
38
+ address: string | null;
39
+ loading: boolean;
40
+ error: string | null;
41
+ };
42
+ /**
43
+ * Display name hook: .mon name if available, otherwise truncated address
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * function Badge({ address }: { address: string }) {
48
+ * const { displayName, monName } = useMNSDisplay(address)
49
+ * return (
50
+ * <span className={monName ? 'text-purple-400' : 'text-gray-500'}>
51
+ * {displayName}
52
+ * </span>
53
+ * )
54
+ * }
55
+ * ```
56
+ */
57
+ declare function useMNSDisplay(address: string | undefined, config?: MNSClientConfig): {
58
+ displayName: string;
59
+ monName: string | null;
60
+ loading: boolean;
61
+ error: string | null;
62
+ };
63
+ /**
64
+ * On-demand resolver hook: for input fields accepting names or addresses
65
+ *
66
+ * @example
67
+ * ```tsx
68
+ * function SendForm() {
69
+ * const { resolve, address, name, loading, error } = useMNSResolve()
70
+ *
71
+ * return (
72
+ * <div>
73
+ * <input
74
+ * placeholder="Address or .mon name"
75
+ * onChange={(e) => resolve(e.target.value)}
76
+ * />
77
+ * {loading && <span>Resolving...</span>}
78
+ * {error && <span className="text-red-500">{error}</span>}
79
+ * {address && <span className="text-green-500">→ {address}</span>}
80
+ * </div>
81
+ * )
82
+ * }
83
+ * ```
84
+ */
85
+ declare function useMNSResolve(config?: MNSClientConfig): {
86
+ resolve: (input: string) => Promise<void>;
87
+ address: string | null;
88
+ name: string | null;
89
+ loading: boolean;
90
+ error: string | null;
91
+ };
92
+ /**
93
+ * Text record hook: get metadata like avatar, URL, twitter handle
94
+ *
95
+ * @example
96
+ * ```tsx
97
+ * function Avatar({ name }: { name: string }) {
98
+ * const { value } = useMNSText(name, 'avatar')
99
+ * return value ? <img src={value} /> : null
100
+ * }
101
+ * ```
102
+ */
103
+ declare function useMNSText(name: string | undefined, key: string, config?: MNSClientConfig): {
104
+ value: string | null;
105
+ loading: boolean;
106
+ error: string | null;
107
+ };
108
+
109
+ export { type MNSClientConfig, useMNSAddress, useMNSDisplay, useMNSName, useMNSResolve, useMNSText };
package/dist/react.js ADDED
@@ -0,0 +1,297 @@
1
+ // src/react.ts
2
+ import { useState, useEffect, useCallback } from "react";
3
+
4
+ // src/resolve.ts
5
+ import { namehash } from "viem";
6
+
7
+ // src/client.ts
8
+ import {
9
+ createPublicClient,
10
+ http
11
+ } from "viem";
12
+ import { monad } from "viem/chains";
13
+ var defaultClient = null;
14
+ function getMNSClient(config) {
15
+ if (config?.client) return config.client;
16
+ if (config?.rpcUrl) {
17
+ return createPublicClient({
18
+ chain: monad,
19
+ transport: http(config.rpcUrl)
20
+ });
21
+ }
22
+ if (!defaultClient) {
23
+ defaultClient = createPublicClient({
24
+ chain: monad,
25
+ transport: http()
26
+ });
27
+ }
28
+ return defaultClient;
29
+ }
30
+
31
+ // src/constants.ts
32
+ var MNS_REGISTRY = "0x13f963486e741c8d3fcdc0a34a910920339a19ce";
33
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
34
+ var registryAbi = [
35
+ {
36
+ name: "resolver",
37
+ type: "function",
38
+ inputs: [{ name: "node", type: "bytes32" }],
39
+ outputs: [{ name: "", type: "address" }],
40
+ stateMutability: "view"
41
+ }
42
+ ];
43
+ var resolverAbi = [
44
+ {
45
+ name: "addr",
46
+ type: "function",
47
+ inputs: [{ name: "node", type: "bytes32" }],
48
+ outputs: [{ name: "", type: "address" }],
49
+ stateMutability: "view"
50
+ },
51
+ {
52
+ name: "name",
53
+ type: "function",
54
+ inputs: [{ name: "node", type: "bytes32" }],
55
+ outputs: [{ name: "", type: "string" }],
56
+ stateMutability: "view"
57
+ },
58
+ {
59
+ name: "text",
60
+ type: "function",
61
+ inputs: [
62
+ { name: "node", type: "bytes32" },
63
+ { name: "key", type: "string" }
64
+ ],
65
+ outputs: [{ name: "", type: "string" }],
66
+ stateMutability: "view"
67
+ }
68
+ ];
69
+
70
+ // src/resolve.ts
71
+ var nameCache = /* @__PURE__ */ new Map();
72
+ var addrCache = /* @__PURE__ */ new Map();
73
+ var CACHE_TTL = 6e4;
74
+ function getCached(cache, key) {
75
+ const entry = cache.get(key);
76
+ if (entry && Date.now() - entry.ts < CACHE_TTL) return entry.value;
77
+ return void 0;
78
+ }
79
+ async function resolveName(name, config) {
80
+ const key = name.toLowerCase();
81
+ const cached = getCached(addrCache, key);
82
+ if (cached !== void 0) return cached;
83
+ try {
84
+ const client = getMNSClient(config);
85
+ const node = namehash(key);
86
+ const resolver = await client.readContract({
87
+ address: MNS_REGISTRY,
88
+ abi: registryAbi,
89
+ functionName: "resolver",
90
+ args: [node]
91
+ });
92
+ if (resolver === ZERO_ADDRESS) {
93
+ addrCache.set(key, { value: null, ts: Date.now() });
94
+ return null;
95
+ }
96
+ const addr = await client.readContract({
97
+ address: resolver,
98
+ abi: resolverAbi,
99
+ functionName: "addr",
100
+ args: [node]
101
+ });
102
+ const result = addr === ZERO_ADDRESS ? null : addr;
103
+ addrCache.set(key, { value: result, ts: Date.now() });
104
+ return result;
105
+ } catch {
106
+ addrCache.set(key, { value: null, ts: Date.now() });
107
+ return null;
108
+ }
109
+ }
110
+ async function lookupAddress(address, config) {
111
+ const key = address.toLowerCase();
112
+ const cached = getCached(nameCache, key);
113
+ if (cached !== void 0) return cached;
114
+ try {
115
+ const client = getMNSClient(config);
116
+ const reverseNode = namehash(
117
+ `${key.slice(2)}.addr.reverse`
118
+ );
119
+ const resolver = await client.readContract({
120
+ address: MNS_REGISTRY,
121
+ abi: registryAbi,
122
+ functionName: "resolver",
123
+ args: [reverseNode]
124
+ });
125
+ if (resolver === ZERO_ADDRESS) {
126
+ nameCache.set(key, { value: null, ts: Date.now() });
127
+ return null;
128
+ }
129
+ const name = await client.readContract({
130
+ address: resolver,
131
+ abi: resolverAbi,
132
+ functionName: "name",
133
+ args: [reverseNode]
134
+ });
135
+ const result = name || null;
136
+ nameCache.set(key, { value: result, ts: Date.now() });
137
+ return result;
138
+ } catch {
139
+ nameCache.set(key, { value: null, ts: Date.now() });
140
+ return null;
141
+ }
142
+ }
143
+ async function getTextRecord(name, key, config) {
144
+ try {
145
+ const client = getMNSClient(config);
146
+ const node = namehash(name.toLowerCase());
147
+ const resolver = await client.readContract({
148
+ address: MNS_REGISTRY,
149
+ abi: registryAbi,
150
+ functionName: "resolver",
151
+ args: [node]
152
+ });
153
+ if (resolver === ZERO_ADDRESS) return null;
154
+ const value = await client.readContract({
155
+ address: resolver,
156
+ abi: resolverAbi,
157
+ functionName: "text",
158
+ args: [node, key]
159
+ });
160
+ return value || null;
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
165
+
166
+ // src/react.ts
167
+ function useMNSName(address, config) {
168
+ const [name, setName] = useState(null);
169
+ const [loading, setLoading] = useState(false);
170
+ const [error, setError] = useState(null);
171
+ useEffect(() => {
172
+ if (!address || !address.startsWith("0x") || address.length !== 42) {
173
+ setName(null);
174
+ return;
175
+ }
176
+ let cancelled = false;
177
+ setLoading(true);
178
+ setError(null);
179
+ lookupAddress(address, config).then((n) => {
180
+ if (!cancelled) setName(n);
181
+ }).catch((e) => {
182
+ if (!cancelled) setError(e.message);
183
+ }).finally(() => {
184
+ if (!cancelled) setLoading(false);
185
+ });
186
+ return () => {
187
+ cancelled = true;
188
+ };
189
+ }, [address]);
190
+ return { name, loading, error };
191
+ }
192
+ function useMNSAddress(name, config) {
193
+ const [address, setAddress] = useState(null);
194
+ const [loading, setLoading] = useState(false);
195
+ const [error, setError] = useState(null);
196
+ useEffect(() => {
197
+ if (!name) {
198
+ setAddress(null);
199
+ return;
200
+ }
201
+ const fullName = name.endsWith(".mon") ? name : `${name}.mon`;
202
+ let cancelled = false;
203
+ setLoading(true);
204
+ setError(null);
205
+ resolveName(fullName, config).then((a) => {
206
+ if (!cancelled) setAddress(a);
207
+ }).catch((e) => {
208
+ if (!cancelled) setError(e.message);
209
+ }).finally(() => {
210
+ if (!cancelled) setLoading(false);
211
+ });
212
+ return () => {
213
+ cancelled = true;
214
+ };
215
+ }, [name]);
216
+ return { address, loading, error };
217
+ }
218
+ function useMNSDisplay(address, config) {
219
+ const { name, loading, error } = useMNSName(address, config);
220
+ const truncated = address ? `${address.slice(0, 6)}...${address.slice(-4)}` : "";
221
+ return {
222
+ displayName: name || truncated,
223
+ monName: name,
224
+ loading,
225
+ error
226
+ };
227
+ }
228
+ function useMNSResolve(config) {
229
+ const [address, setAddress] = useState(null);
230
+ const [name, setName] = useState(null);
231
+ const [loading, setLoading] = useState(false);
232
+ const [error, setError] = useState(null);
233
+ const resolve = useCallback(async (input) => {
234
+ const trimmed = input.trim();
235
+ setError(null);
236
+ if (!trimmed) {
237
+ setAddress(null);
238
+ setName(null);
239
+ return;
240
+ }
241
+ if (trimmed.startsWith("0x") && trimmed.length === 42) {
242
+ setAddress(trimmed);
243
+ setLoading(true);
244
+ const n = await lookupAddress(trimmed, config);
245
+ setName(n);
246
+ setLoading(false);
247
+ return;
248
+ }
249
+ const fullName = trimmed.endsWith(".mon") ? trimmed : `${trimmed}.mon`;
250
+ setName(fullName);
251
+ setLoading(true);
252
+ try {
253
+ const addr = await resolveName(fullName, config);
254
+ setAddress(addr);
255
+ if (!addr) setError(`${fullName} not found`);
256
+ } catch (e) {
257
+ setError(e.message);
258
+ setAddress(null);
259
+ } finally {
260
+ setLoading(false);
261
+ }
262
+ }, []);
263
+ return { resolve, address, name, loading, error };
264
+ }
265
+ function useMNSText(name, key, config) {
266
+ const [value, setValue] = useState(null);
267
+ const [loading, setLoading] = useState(false);
268
+ const [error, setError] = useState(null);
269
+ useEffect(() => {
270
+ if (!name) {
271
+ setValue(null);
272
+ return;
273
+ }
274
+ let cancelled = false;
275
+ setLoading(true);
276
+ setError(null);
277
+ getTextRecord(name, key, config).then((v) => {
278
+ if (!cancelled) setValue(v);
279
+ }).catch((e) => {
280
+ if (!cancelled) setError(e.message);
281
+ }).finally(() => {
282
+ if (!cancelled) setLoading(false);
283
+ });
284
+ return () => {
285
+ cancelled = true;
286
+ };
287
+ }, [name, key]);
288
+ return { value, loading, error };
289
+ }
290
+ export {
291
+ useMNSAddress,
292
+ useMNSDisplay,
293
+ useMNSName,
294
+ useMNSResolve,
295
+ useMNSText
296
+ };
297
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.ts","../src/resolve.ts","../src/client.ts","../src/constants.ts"],"sourcesContent":["import { useState, useEffect, useCallback } from 'react';\nimport { resolveName, lookupAddress, getTextRecord } from './resolve';\nimport { type MNSClientConfig } from './client';\n\nexport type { MNSClientConfig };\n\n/**\n * Reverse resolution hook: address → .mon name\n *\n * @example\n * ```tsx\n * function Profile({ address }: { address: string }) {\n * const { name, loading } = useMNSName(address)\n * return <span>{loading ? '...' : name ?? address}</span>\n * }\n * ```\n */\nexport function useMNSName(\n address: string | undefined,\n config?: MNSClientConfig,\n) {\n const [name, setName] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!address || !address.startsWith('0x') || address.length !== 42) {\n setName(null);\n return;\n }\n\n let cancelled = false;\n setLoading(true);\n setError(null);\n\n lookupAddress(address, config)\n .then((n) => {\n if (!cancelled) setName(n);\n })\n .catch((e) => {\n if (!cancelled) setError(e.message);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [address]);\n\n return { name, loading, error };\n}\n\n/**\n * Forward resolution hook: .mon name → address\n *\n * @example\n * ```tsx\n * function Lookup() {\n * const { address, loading } = useMNSAddress('alice.mon')\n * return <span>{loading ? 'Resolving...' : address}</span>\n * }\n * ```\n */\nexport function useMNSAddress(\n name: string | undefined,\n config?: MNSClientConfig,\n) {\n const [address, setAddress] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!name) {\n setAddress(null);\n return;\n }\n const fullName = name.endsWith('.mon') ? name : `${name}.mon`;\n\n let cancelled = false;\n setLoading(true);\n setError(null);\n\n resolveName(fullName, config)\n .then((a) => {\n if (!cancelled) setAddress(a);\n })\n .catch((e) => {\n if (!cancelled) setError(e.message);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [name]);\n\n return { address, loading, error };\n}\n\n/**\n * Display name hook: .mon name if available, otherwise truncated address\n *\n * @example\n * ```tsx\n * function Badge({ address }: { address: string }) {\n * const { displayName, monName } = useMNSDisplay(address)\n * return (\n * <span className={monName ? 'text-purple-400' : 'text-gray-500'}>\n * {displayName}\n * </span>\n * )\n * }\n * ```\n */\nexport function useMNSDisplay(\n address: string | undefined,\n config?: MNSClientConfig,\n) {\n const { name, loading, error } = useMNSName(address, config);\n const truncated = address\n ? `${address.slice(0, 6)}...${address.slice(-4)}`\n : '';\n\n return {\n displayName: name || truncated,\n monName: name,\n loading,\n error,\n };\n}\n\n/**\n * On-demand resolver hook: for input fields accepting names or addresses\n *\n * @example\n * ```tsx\n * function SendForm() {\n * const { resolve, address, name, loading, error } = useMNSResolve()\n *\n * return (\n * <div>\n * <input\n * placeholder=\"Address or .mon name\"\n * onChange={(e) => resolve(e.target.value)}\n * />\n * {loading && <span>Resolving...</span>}\n * {error && <span className=\"text-red-500\">{error}</span>}\n * {address && <span className=\"text-green-500\">→ {address}</span>}\n * </div>\n * )\n * }\n * ```\n */\nexport function useMNSResolve(config?: MNSClientConfig) {\n const [address, setAddress] = useState<string | null>(null);\n const [name, setName] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const resolve = useCallback(async (input: string) => {\n const trimmed = input.trim();\n setError(null);\n\n if (!trimmed) {\n setAddress(null);\n setName(null);\n return;\n }\n\n // Direct address\n if (trimmed.startsWith('0x') && trimmed.length === 42) {\n setAddress(trimmed);\n setLoading(true);\n const n = await lookupAddress(trimmed, config);\n setName(n);\n setLoading(false);\n return;\n }\n\n // Name input\n const fullName = trimmed.endsWith('.mon') ? trimmed : `${trimmed}.mon`;\n setName(fullName);\n setLoading(true);\n\n try {\n const addr = await resolveName(fullName, config);\n setAddress(addr);\n if (!addr) setError(`${fullName} not found`);\n } catch (e: any) {\n setError(e.message);\n setAddress(null);\n } finally {\n setLoading(false);\n }\n }, []);\n\n return { resolve, address, name, loading, error };\n}\n\n/**\n * Text record hook: get metadata like avatar, URL, twitter handle\n *\n * @example\n * ```tsx\n * function Avatar({ name }: { name: string }) {\n * const { value } = useMNSText(name, 'avatar')\n * return value ? <img src={value} /> : null\n * }\n * ```\n */\nexport function useMNSText(\n name: string | undefined,\n key: string,\n config?: MNSClientConfig,\n) {\n const [value, setValue] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!name) {\n setValue(null);\n return;\n }\n\n let cancelled = false;\n setLoading(true);\n setError(null);\n\n getTextRecord(name, key, config)\n .then((v) => {\n if (!cancelled) setValue(v);\n })\n .catch((e) => {\n if (!cancelled) setError(e.message);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [name, key]);\n\n return { value, loading, error };\n}\n","import { namehash } from 'viem';\nimport { getMNSClient, type MNSClientConfig } from './client';\nimport {\n MNS_REGISTRY,\n ZERO_ADDRESS,\n registryAbi,\n resolverAbi,\n} from './constants';\n\n// ─── Cache ─────────────────────────────────────────────────────\n\nconst nameCache = new Map<string, { value: string | null; ts: number }>();\nconst addrCache = new Map<string, { value: string | null; ts: number }>();\n\nconst CACHE_TTL = 60_000; // 1 minute\n\nfunction getCached(\n cache: Map<string, { value: string | null; ts: number }>,\n key: string,\n) {\n const entry = cache.get(key);\n if (entry && Date.now() - entry.ts < CACHE_TTL) return entry.value;\n return undefined;\n}\n\n// ─── Forward Resolution ────────────────────────────────────────\n\n/**\n * Resolve a .mon name to an address.\n *\n * @param name - Full name including .mon (e.g. \"alice.mon\")\n * @param config - Optional client configuration\n * @returns The resolved address, or null if not found\n *\n * @example\n * ```ts\n * import { resolveName } from '@monadns/sdk'\n *\n * const address = await resolveName('alice.mon')\n * // '0xa05a8BF1eda5bbC2b3aCAF03D04f77bD7d66Cc47'\n * ```\n */\nexport async function resolveName(\n name: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const key = name.toLowerCase();\n const cached = getCached(addrCache, key);\n if (cached !== undefined) return cached;\n\n try {\n const client = getMNSClient(config);\n const node = namehash(key) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [node],\n });\n\n if (resolver === ZERO_ADDRESS) {\n addrCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n\n const addr = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'addr',\n args: [node],\n });\n\n const result = addr === ZERO_ADDRESS ? null : addr;\n addrCache.set(key, { value: result, ts: Date.now() });\n return result;\n } catch {\n addrCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n}\n\n// ─── Reverse Resolution ────────────────────────────────────────\n\n/**\n * Look up the primary .mon name for an address.\n *\n * @param address - A 0x-prefixed Monad address\n * @param config - Optional client configuration\n * @returns The primary name (e.g. \"alice.mon\"), or null\n *\n * @example\n * ```ts\n * import { lookupAddress } from '@monadns/sdk'\n *\n * const name = await lookupAddress('0xa05a8BF1...')\n * // 'alice.mon'\n * ```\n */\nexport async function lookupAddress(\n address: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const key = address.toLowerCase();\n const cached = getCached(nameCache, key);\n if (cached !== undefined) return cached;\n\n try {\n const client = getMNSClient(config);\n const reverseNode = namehash(\n `${key.slice(2)}.addr.reverse`,\n ) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [reverseNode],\n });\n\n if (resolver === ZERO_ADDRESS) {\n nameCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n\n const name = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'name',\n args: [reverseNode],\n });\n\n const result = name || null;\n nameCache.set(key, { value: result, ts: Date.now() });\n return result;\n } catch {\n nameCache.set(key, { value: null, ts: Date.now() });\n return null;\n }\n}\n\n// ─── Text Records ──────────────────────────────────────────────\n\n/**\n * Get a text record for a .mon name.\n *\n * @param name - Full name including .mon\n * @param key - The text record key (e.g. \"avatar\", \"url\", \"com.twitter\")\n * @param config - Optional client configuration\n *\n * @example\n * ```ts\n * const avatar = await getTextRecord('alice.mon', 'avatar')\n * const twitter = await getTextRecord('alice.mon', 'com.twitter')\n * ```\n */\nexport async function getTextRecord(\n name: string,\n key: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n try {\n const client = getMNSClient(config);\n const node = namehash(name.toLowerCase()) as `0x${string}`;\n\n const resolver = await client.readContract({\n address: MNS_REGISTRY,\n abi: registryAbi,\n functionName: 'resolver',\n args: [node],\n });\n\n if (resolver === ZERO_ADDRESS) return null;\n\n const value = await client.readContract({\n address: resolver,\n abi: resolverAbi,\n functionName: 'text',\n args: [node, key],\n });\n\n return value || null;\n } catch {\n return null;\n }\n}\n\n// ─── Helpers ───────────────────────────────────────────────────\n\n/**\n * Get a display-friendly name for an address.\n * Returns the .mon name if set, otherwise a truncated address.\n *\n * @example\n * ```ts\n * const display = await getDisplayName('0xa05a8BF1...')\n * // 'alice.mon' or '0xa05a...Cc47'\n * ```\n */\nexport async function getDisplayName(\n address: string,\n config?: MNSClientConfig,\n): Promise<string> {\n const name = await lookupAddress(address, config);\n if (name) return name;\n return `${address.slice(0, 6)}...${address.slice(-4)}`;\n}\n\n/**\n * Smart resolver — accepts either a .mon name or a raw address.\n * Useful for input fields where users can type either.\n *\n * @returns The resolved address, or null if invalid/not found\n *\n * @example\n * ```ts\n * const addr = await resolveInput('alice.mon') // resolves name\n * const addr = await resolveInput('alice') // tries alice.mon\n * const addr = await resolveInput('0xa05a...') // passes through\n * ```\n */\nexport async function resolveInput(\n input: string,\n config?: MNSClientConfig,\n): Promise<string | null> {\n const trimmed = input.trim();\n if (!trimmed) return null;\n\n if (trimmed.startsWith('0x') && trimmed.length === 42) return trimmed;\n if (trimmed.endsWith('.mon')) return resolveName(trimmed, config);\n\n // Try appending .mon\n return resolveName(`${trimmed}.mon`, config);\n}\n\n/**\n * Check if a .mon name is registered.\n *\n * @example\n * ```ts\n * const taken = await isRegistered('alice.mon')\n * ```\n */\nexport async function isRegistered(\n name: string,\n config?: MNSClientConfig,\n): Promise<boolean> {\n const addr = await resolveName(name, config);\n return addr !== null;\n}\n\n/**\n * 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;\n\nexport const ZERO_ADDRESS =\n '0x0000000000000000000000000000000000000000' as const;\n\nexport const registryAbi = [\n {\n name: 'resolver',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'address' }],\n stateMutability: 'view',\n },\n] as const;\n\nexport const resolverAbi = [\n {\n name: 'addr',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'address' }],\n stateMutability: 'view',\n },\n {\n name: 'name',\n type: 'function',\n inputs: [{ name: 'node', type: 'bytes32' }],\n outputs: [{ name: '', type: 'string' }],\n stateMutability: 'view',\n },\n {\n name: 'text',\n type: 'function',\n inputs: [\n { name: 'node', type: 'bytes32' },\n { name: 'key', type: 'string' },\n ],\n outputs: [{ name: '', type: 'string' }],\n stateMutability: 'view',\n },\n] as const;\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@monadns/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Monad Name Service SDK — resolve .mon names on Monad",
5
+ "author": "Monad Name Service",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/monadns/sdk"
10
+ },
11
+ "keywords": [
12
+ "monad",
13
+ "ens",
14
+ "name-service",
15
+ "web3",
16
+ "dns",
17
+ ".mon"
18
+ ],
19
+ "type": "module",
20
+ "main": "./dist/index.cjs",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
28
+ },
29
+ "./react": {
30
+ "types": "./dist/react.d.ts",
31
+ "import": "./dist/react.js",
32
+ "require": "./dist/react.cjs"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "dev": "tsup --watch",
43
+ "lint": "tsc --noEmit",
44
+ "prepublishOnly": "npm run build"
45
+ },
46
+ "dependencies": {
47
+ "viem": "^2.0.0"
48
+ },
49
+ "peerDependencies": {
50
+ "react": ">=17.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "react": {
54
+ "optional": true
55
+ }
56
+ },
57
+ "devDependencies": {
58
+ "@types/react": "^18.0.0",
59
+ "react": "^18.0.0",
60
+ "tsup": "^8.0.0",
61
+ "typescript": "^5.0.0"
62
+ }
63
+ }