@hybrd/xmtp 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.
- package/.cache/tsbuildinfo.json +1 -0
- package/.turbo/turbo-typecheck.log +5 -0
- package/README.md +380 -0
- package/biome.jsonc +4 -0
- package/package.json +46 -0
- package/scripts/generate-keys.ts +25 -0
- package/scripts/refresh-identity.ts +119 -0
- package/scripts/register-wallet.ts +95 -0
- package/scripts/revoke-all-installations.ts +91 -0
- package/scripts/revoke-installations.ts +94 -0
- package/src/abi/l2_resolver.ts +699 -0
- package/src/client.ts +940 -0
- package/src/constants.ts +6 -0
- package/src/index.ts +129 -0
- package/src/lib/message-listener.test.ts +369 -0
- package/src/lib/message-listener.ts +343 -0
- package/src/lib/subjects.ts +89 -0
- package/src/localStorage.ts.old +203 -0
- package/src/resolver/address-resolver.ts +221 -0
- package/src/resolver/basename-resolver.ts +585 -0
- package/src/resolver/ens-resolver.ts +324 -0
- package/src/resolver/index.ts +1 -0
- package/src/resolver/resolver.ts +336 -0
- package/src/resolver/xmtp-resolver.ts +436 -0
- package/src/service-client.ts +286 -0
- package/src/transactionMonitor.ts.old +275 -0
- package/src/types.ts +157 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Address,
|
|
3
|
+
PublicClient,
|
|
4
|
+
encodePacked,
|
|
5
|
+
keccak256,
|
|
6
|
+
namehash
|
|
7
|
+
} from "viem"
|
|
8
|
+
import { mainnet } from "viem/chains"
|
|
9
|
+
import { L2ResolverAbi } from "../abi/l2_resolver"
|
|
10
|
+
|
|
11
|
+
// Base L2 Resolver Address mapping by chain ID
|
|
12
|
+
// const BASENAME_L2_RESOLVER_ADDRESSES: Record<number, Address> = {
|
|
13
|
+
// [mainnet.id]: "0x0000000000000000000000000000000000000000", // Mainnet (1)
|
|
14
|
+
// [base.id]: "0xC6d566A56A1aFf6508b41f6c90ff131615583BCD", // Base Mainnet (8453)
|
|
15
|
+
// [baseSepolia.id]: "0x6533C94869D28fAA8dF77cc63f9e2b2D6Cf77eBA" // Base Sepolia (84532)
|
|
16
|
+
// } as const
|
|
17
|
+
|
|
18
|
+
const BASENAME_L2_RESOLVER_ADDRESS =
|
|
19
|
+
"0xC6d566A56A1aFf6508b41f6c90ff131615583BCD"
|
|
20
|
+
|
|
21
|
+
// Basename text record keys for metadata
|
|
22
|
+
export const BasenameTextRecordKeys = {
|
|
23
|
+
Email: "email",
|
|
24
|
+
Url: "url",
|
|
25
|
+
Avatar: "avatar",
|
|
26
|
+
Description: "description",
|
|
27
|
+
Notice: "notice",
|
|
28
|
+
Keywords: "keywords",
|
|
29
|
+
Twitter: "com.twitter",
|
|
30
|
+
Github: "com.github",
|
|
31
|
+
Discord: "com.discord",
|
|
32
|
+
Telegram: "org.telegram",
|
|
33
|
+
Snapshot: "snapshot",
|
|
34
|
+
Location: "location"
|
|
35
|
+
} as const
|
|
36
|
+
|
|
37
|
+
export type BasenameTextRecordKey =
|
|
38
|
+
(typeof BasenameTextRecordKeys)[keyof typeof BasenameTextRecordKeys]
|
|
39
|
+
export type BaseName = string
|
|
40
|
+
|
|
41
|
+
interface BasenameResolverOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Maximum number of basenames to cache
|
|
44
|
+
* @default 500
|
|
45
|
+
*/
|
|
46
|
+
maxCacheSize?: number
|
|
47
|
+
/**
|
|
48
|
+
* Cache TTL in milliseconds
|
|
49
|
+
* @default 3600000 (1 hour)
|
|
50
|
+
*/
|
|
51
|
+
cacheTtl?: number
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Public client
|
|
55
|
+
* @default null
|
|
56
|
+
*/
|
|
57
|
+
publicClient: PublicClient
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface CacheEntry {
|
|
61
|
+
basename: string
|
|
62
|
+
timestamp: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface TextRecordCacheEntry {
|
|
66
|
+
value: string
|
|
67
|
+
timestamp: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert an chainId to a coinType hex for reverse chain resolution
|
|
72
|
+
*/
|
|
73
|
+
export const convertChainIdToCoinType = (chainId: number): string => {
|
|
74
|
+
// L1 resolvers to addr
|
|
75
|
+
if (chainId === mainnet.id) {
|
|
76
|
+
return "addr"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const cointype = (0x80000000 | chainId) >>> 0
|
|
80
|
+
return cointype.toString(16).toLocaleUpperCase()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Helper function to convert an address to its reverse node for ENS lookups
|
|
85
|
+
*/
|
|
86
|
+
export const convertReverseNodeToBytes = (
|
|
87
|
+
address: Address,
|
|
88
|
+
chainId: number
|
|
89
|
+
) => {
|
|
90
|
+
const addressFormatted = address.toLocaleLowerCase() as Address
|
|
91
|
+
const addressNode = keccak256(addressFormatted.substring(2) as Address)
|
|
92
|
+
const chainCoinType = convertChainIdToCoinType(chainId)
|
|
93
|
+
const baseReverseNode = namehash(
|
|
94
|
+
`${chainCoinType.toLocaleUpperCase()}.reverse`
|
|
95
|
+
)
|
|
96
|
+
const addressReverseNode = keccak256(
|
|
97
|
+
encodePacked(["bytes32", "bytes32"], [baseReverseNode, addressNode])
|
|
98
|
+
)
|
|
99
|
+
return addressReverseNode
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Helper function to convert a basename to its node hash
|
|
104
|
+
*/
|
|
105
|
+
function convertBasenameToNode(basename: string): `0x${string}` {
|
|
106
|
+
return namehash(basename)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get the resolver address for a given chain ID
|
|
111
|
+
*/
|
|
112
|
+
function getResolverAddress(): Address {
|
|
113
|
+
const resolverAddress = BASENAME_L2_RESOLVER_ADDRESS
|
|
114
|
+
return resolverAddress
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class BasenameResolver {
|
|
118
|
+
private cache = new Map<string, CacheEntry>()
|
|
119
|
+
private textRecordCache = new Map<string, Map<string, TextRecordCacheEntry>>()
|
|
120
|
+
private readonly maxCacheSize: number
|
|
121
|
+
private readonly cacheTtl: number
|
|
122
|
+
private readonly baseClient: PublicClient
|
|
123
|
+
private resolverAddress: Address | null = null
|
|
124
|
+
private chainId: number | null = null
|
|
125
|
+
|
|
126
|
+
constructor(options: BasenameResolverOptions) {
|
|
127
|
+
this.maxCacheSize = options.maxCacheSize ?? 500
|
|
128
|
+
this.cacheTtl = options.cacheTtl ?? 3600000 // 1 hour
|
|
129
|
+
|
|
130
|
+
// Create a public client for Base network
|
|
131
|
+
this.baseClient = options.publicClient
|
|
132
|
+
|
|
133
|
+
// Initialize resolver address lazily on first use
|
|
134
|
+
this.initializeResolver()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Initialize the resolver address based on the client's chain ID
|
|
139
|
+
*/
|
|
140
|
+
private async initializeResolver(): Promise<void> {
|
|
141
|
+
if (this.resolverAddress && this.chainId) {
|
|
142
|
+
console.log(
|
|
143
|
+
`🔄 BasenameResolver already initialized for chain ${this.chainId} with resolver ${this.resolverAddress}`
|
|
144
|
+
)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
console.log("🔄 Initializing BasenameResolver...")
|
|
150
|
+
this.chainId = await this.baseClient.getChainId()
|
|
151
|
+
console.log(`🔗 Chain ID detected: ${this.chainId}`)
|
|
152
|
+
|
|
153
|
+
this.resolverAddress = getResolverAddress()
|
|
154
|
+
console.log(
|
|
155
|
+
`📍 Resolver address for chain ${this.chainId}: ${this.resolverAddress}`
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
console.log(
|
|
159
|
+
`✅ Initialized BasenameResolver for chain ${this.chainId} with resolver ${this.resolverAddress}`
|
|
160
|
+
)
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error("❌ Failed to initialize BasenameResolver:", error)
|
|
163
|
+
throw error
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get the resolver address, initializing if necessary
|
|
169
|
+
*/
|
|
170
|
+
private async getResolverAddress(): Promise<Address> {
|
|
171
|
+
await this.initializeResolver()
|
|
172
|
+
if (!this.resolverAddress) {
|
|
173
|
+
throw new Error("Failed to initialize resolver address")
|
|
174
|
+
}
|
|
175
|
+
return this.resolverAddress
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Resolve a basename from an Ethereum address
|
|
180
|
+
*/
|
|
181
|
+
async getBasename(address: Address): Promise<string | null> {
|
|
182
|
+
console.log(`🔍 Starting basename resolution for address: ${address}`)
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// Check cache first
|
|
186
|
+
const cached = this.getCachedBasename(address)
|
|
187
|
+
if (cached) {
|
|
188
|
+
console.log(`✅ Resolved basename from cache: ${cached}`)
|
|
189
|
+
return cached
|
|
190
|
+
}
|
|
191
|
+
console.log(`📭 No cached basename found for address: ${address}`)
|
|
192
|
+
|
|
193
|
+
console.log("🔄 Getting resolver address...")
|
|
194
|
+
const resolverAddress = await this.getResolverAddress()
|
|
195
|
+
console.log(`📍 Using resolver address: ${resolverAddress}`)
|
|
196
|
+
|
|
197
|
+
console.log("🔄 Getting chain ID...")
|
|
198
|
+
const chainId = await this.baseClient.getChainId()
|
|
199
|
+
console.log(`🔗 Chain ID: ${chainId}`)
|
|
200
|
+
|
|
201
|
+
console.log("🔄 Converting address to reverse node...")
|
|
202
|
+
const addressReverseNode = convertReverseNodeToBytes(
|
|
203
|
+
// address.toUpperCase() as `0x${string}`,
|
|
204
|
+
address as `0x${string}`,
|
|
205
|
+
chainId
|
|
206
|
+
)
|
|
207
|
+
console.log(`🔗 Reverse node: ${addressReverseNode}`)
|
|
208
|
+
|
|
209
|
+
console.log("🔄 Reading contract to resolve basename...")
|
|
210
|
+
const basename = await this.baseClient.readContract({
|
|
211
|
+
abi: L2ResolverAbi,
|
|
212
|
+
address: resolverAddress,
|
|
213
|
+
functionName: "name",
|
|
214
|
+
args: [addressReverseNode]
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
console.log(
|
|
218
|
+
`📋 Contract returned basename: "${basename}" (length: ${basename?.length || 0})`
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if (basename && basename.length > 0) {
|
|
222
|
+
this.setCachedBasename(address, basename)
|
|
223
|
+
console.log(`✅ Resolved basename: ${basename} for address: ${address}`)
|
|
224
|
+
return basename as BaseName
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(
|
|
228
|
+
`❌ No basename found for address: ${address} (empty or null response)`
|
|
229
|
+
)
|
|
230
|
+
return null
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error(
|
|
233
|
+
`❌ Error resolving basename for address ${address}:`,
|
|
234
|
+
error
|
|
235
|
+
)
|
|
236
|
+
if (error instanceof Error) {
|
|
237
|
+
console.error(`❌ Error details: ${error.message}`)
|
|
238
|
+
console.error(`❌ Error stack:`, error.stack)
|
|
239
|
+
}
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get the avatar URL for a basename
|
|
246
|
+
*/
|
|
247
|
+
async getBasenameAvatar(basename: BaseName): Promise<string | null> {
|
|
248
|
+
console.log(`🖼️ Getting avatar for basename: ${basename}`)
|
|
249
|
+
return this.getBasenameTextRecord(basename, BasenameTextRecordKeys.Avatar)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get a text record for a basename
|
|
254
|
+
*/
|
|
255
|
+
async getBasenameTextRecord(
|
|
256
|
+
basename: BaseName,
|
|
257
|
+
key: BasenameTextRecordKey
|
|
258
|
+
): Promise<string | null> {
|
|
259
|
+
console.log(`📝 Getting text record "${key}" for basename: ${basename}`)
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
// Check cache first
|
|
263
|
+
const cached = this.getCachedTextRecord(basename, key)
|
|
264
|
+
if (cached) {
|
|
265
|
+
console.log(`✅ Resolved text record from cache: ${key}=${cached}`)
|
|
266
|
+
return cached
|
|
267
|
+
}
|
|
268
|
+
console.log(`📭 No cached text record found for ${basename}.${key}`)
|
|
269
|
+
|
|
270
|
+
console.log("🔄 Getting resolver address...")
|
|
271
|
+
const resolverAddress = await this.getResolverAddress()
|
|
272
|
+
console.log(`📍 Using resolver address: ${resolverAddress}`)
|
|
273
|
+
|
|
274
|
+
console.log("🔄 Converting basename to node...")
|
|
275
|
+
const node = convertBasenameToNode(basename)
|
|
276
|
+
console.log(`🔗 Node hash: ${node}`)
|
|
277
|
+
|
|
278
|
+
console.log(`🔄 Reading contract for text record "${key}"...`)
|
|
279
|
+
const textRecord = await this.baseClient.readContract({
|
|
280
|
+
abi: L2ResolverAbi,
|
|
281
|
+
address: resolverAddress,
|
|
282
|
+
functionName: "text",
|
|
283
|
+
args: [node, key]
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
console.log(
|
|
287
|
+
`📋 Contract returned text record: "${textRecord}" (length: ${textRecord?.length || 0})`
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if (textRecord && textRecord.length > 0) {
|
|
291
|
+
this.setCachedTextRecord(basename, key, textRecord)
|
|
292
|
+
console.log(`✅ Resolved text record: ${key}=${textRecord}`)
|
|
293
|
+
return textRecord
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log(
|
|
297
|
+
`❌ No text record found for ${basename}.${key} (empty or null response)`
|
|
298
|
+
)
|
|
299
|
+
return null
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error(
|
|
302
|
+
`❌ Error resolving text record ${key} for ${basename}:`,
|
|
303
|
+
error
|
|
304
|
+
)
|
|
305
|
+
if (error instanceof Error) {
|
|
306
|
+
console.error(`❌ Error details: ${error.message}`)
|
|
307
|
+
console.error(`❌ Error stack:`, error.stack)
|
|
308
|
+
}
|
|
309
|
+
return null
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get the Ethereum address that owns a basename
|
|
315
|
+
*/
|
|
316
|
+
async getBasenameAddress(basename: BaseName): Promise<Address | null> {
|
|
317
|
+
console.log(`🔍 Getting address for basename: ${basename}`)
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
console.log("🔄 Getting resolver address...")
|
|
321
|
+
const resolverAddress = await this.getResolverAddress()
|
|
322
|
+
console.log(`📍 Using resolver address: ${resolverAddress}`)
|
|
323
|
+
|
|
324
|
+
console.log("🔄 Converting basename to node...")
|
|
325
|
+
const node = convertBasenameToNode(basename)
|
|
326
|
+
console.log(`🔗 Node hash: ${node}`)
|
|
327
|
+
|
|
328
|
+
console.log("🔄 Reading contract to resolve address...")
|
|
329
|
+
const address = await this.baseClient.readContract({
|
|
330
|
+
abi: L2ResolverAbi,
|
|
331
|
+
address: resolverAddress,
|
|
332
|
+
functionName: "addr",
|
|
333
|
+
args: [node]
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
console.log(`📋 Contract returned address: "${address}"`)
|
|
337
|
+
|
|
338
|
+
if (address && address !== "0x0000000000000000000000000000000000000000") {
|
|
339
|
+
console.log(`✅ Resolved address: ${address} for basename: ${basename}`)
|
|
340
|
+
return address as Address
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(
|
|
344
|
+
`❌ No address found for basename: ${basename} (zero address or null response)`
|
|
345
|
+
)
|
|
346
|
+
return null
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error(
|
|
349
|
+
`❌ Error resolving address for basename ${basename}:`,
|
|
350
|
+
error
|
|
351
|
+
)
|
|
352
|
+
if (error instanceof Error) {
|
|
353
|
+
console.error(`❌ Error details: ${error.message}`)
|
|
354
|
+
console.error(`❌ Error stack:`, error.stack)
|
|
355
|
+
}
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get all basic metadata for a basename
|
|
362
|
+
*/
|
|
363
|
+
async getBasenameMetadata(basename: BaseName) {
|
|
364
|
+
console.log(`📊 Getting metadata for basename: ${basename}`)
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const [avatar, description, twitter, github, url] = await Promise.all([
|
|
368
|
+
this.getBasenameTextRecord(basename, BasenameTextRecordKeys.Avatar),
|
|
369
|
+
this.getBasenameTextRecord(
|
|
370
|
+
basename,
|
|
371
|
+
BasenameTextRecordKeys.Description
|
|
372
|
+
),
|
|
373
|
+
this.getBasenameTextRecord(basename, BasenameTextRecordKeys.Twitter),
|
|
374
|
+
this.getBasenameTextRecord(basename, BasenameTextRecordKeys.Github),
|
|
375
|
+
this.getBasenameTextRecord(basename, BasenameTextRecordKeys.Url)
|
|
376
|
+
])
|
|
377
|
+
|
|
378
|
+
const metadata = {
|
|
379
|
+
basename,
|
|
380
|
+
avatar,
|
|
381
|
+
description,
|
|
382
|
+
twitter,
|
|
383
|
+
github,
|
|
384
|
+
url
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
console.log(`✅ Resolved metadata for ${basename}:`, metadata)
|
|
388
|
+
return metadata
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.error(
|
|
391
|
+
`❌ Error resolving metadata for basename ${basename}:`,
|
|
392
|
+
error
|
|
393
|
+
)
|
|
394
|
+
if (error instanceof Error) {
|
|
395
|
+
console.error(`❌ Error details: ${error.message}`)
|
|
396
|
+
console.error(`❌ Error stack:`, error.stack)
|
|
397
|
+
}
|
|
398
|
+
return null
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Resolve a full basename profile (name + metadata) from an address
|
|
404
|
+
*/
|
|
405
|
+
async resolveBasenameProfile(address: Address) {
|
|
406
|
+
console.log(`👤 Resolving full basename profile for address: ${address}`)
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
const basename = await this.getBasename(address)
|
|
410
|
+
if (!basename) {
|
|
411
|
+
console.log(`❌ No basename found for address: ${address}`)
|
|
412
|
+
return null
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
console.log(`🔄 Getting metadata for resolved basename: ${basename}`)
|
|
416
|
+
const metadata = await this.getBasenameMetadata(basename)
|
|
417
|
+
|
|
418
|
+
const profile = {
|
|
419
|
+
address,
|
|
420
|
+
...metadata
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
console.log(`✅ Resolved full profile for ${address}:`, profile)
|
|
424
|
+
return profile
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error(
|
|
427
|
+
`❌ Error resolving basename profile for ${address}:`,
|
|
428
|
+
error
|
|
429
|
+
)
|
|
430
|
+
if (error instanceof Error) {
|
|
431
|
+
console.error(`❌ Error details: ${error.message}`)
|
|
432
|
+
console.error(`❌ Error stack:`, error.stack)
|
|
433
|
+
}
|
|
434
|
+
return null
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Get cached basename if not expired
|
|
440
|
+
*/
|
|
441
|
+
private getCachedBasename(address: Address): string | null {
|
|
442
|
+
const entry = this.cache.get(address.toLowerCase())
|
|
443
|
+
if (!entry) {
|
|
444
|
+
console.log(
|
|
445
|
+
`📭 No cache entry found for address: ${address.toLowerCase()}`
|
|
446
|
+
)
|
|
447
|
+
return null
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const now = Date.now()
|
|
451
|
+
const age = now - entry.timestamp
|
|
452
|
+
console.log(`🕐 Cache entry age: ${age}ms (TTL: ${this.cacheTtl}ms)`)
|
|
453
|
+
|
|
454
|
+
if (age > this.cacheTtl) {
|
|
455
|
+
console.log(
|
|
456
|
+
`⏰ Cache entry expired for address: ${address.toLowerCase()}`
|
|
457
|
+
)
|
|
458
|
+
this.cache.delete(address.toLowerCase())
|
|
459
|
+
return null
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
console.log(
|
|
463
|
+
`✅ Valid cache entry found for ${address.toLowerCase()}: "${entry.basename}"`
|
|
464
|
+
)
|
|
465
|
+
return entry.basename
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Cache basename with LRU eviction
|
|
470
|
+
*/
|
|
471
|
+
private setCachedBasename(address: Address, basename: string): void {
|
|
472
|
+
// Simple LRU: if cache is full, remove oldest entry
|
|
473
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
474
|
+
const firstKey = this.cache.keys().next().value
|
|
475
|
+
if (firstKey) {
|
|
476
|
+
console.log(`🗑️ Cache full, removing oldest entry: ${firstKey}`)
|
|
477
|
+
this.cache.delete(firstKey)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
console.log(
|
|
482
|
+
`💾 Caching basename "${basename}" for address: ${address.toLowerCase()}`
|
|
483
|
+
)
|
|
484
|
+
this.cache.set(address.toLowerCase(), {
|
|
485
|
+
basename,
|
|
486
|
+
timestamp: Date.now()
|
|
487
|
+
})
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Get cached text record if not expired
|
|
492
|
+
*/
|
|
493
|
+
private getCachedTextRecord(basename: string, key: string): string | null {
|
|
494
|
+
const basenameCache = this.textRecordCache.get(basename)
|
|
495
|
+
if (!basenameCache) {
|
|
496
|
+
console.log(`📭 No text record cache found for basename: ${basename}`)
|
|
497
|
+
return null
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const entry = basenameCache.get(key)
|
|
501
|
+
if (!entry) {
|
|
502
|
+
console.log(`📭 No cached text record found for ${basename}.${key}`)
|
|
503
|
+
return null
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const now = Date.now()
|
|
507
|
+
const age = now - entry.timestamp
|
|
508
|
+
console.log(
|
|
509
|
+
`🕐 Text record cache entry age: ${age}ms (TTL: ${this.cacheTtl}ms)`
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
if (age > this.cacheTtl) {
|
|
513
|
+
console.log(`⏰ Text record cache entry expired for ${basename}.${key}`)
|
|
514
|
+
basenameCache.delete(key)
|
|
515
|
+
return null
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
console.log(
|
|
519
|
+
`✅ Valid text record cache entry found for ${basename}.${key}: "${entry.value}"`
|
|
520
|
+
)
|
|
521
|
+
return entry.value
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Cache text record
|
|
526
|
+
*/
|
|
527
|
+
private setCachedTextRecord(
|
|
528
|
+
basename: string,
|
|
529
|
+
key: string,
|
|
530
|
+
value: string
|
|
531
|
+
): void {
|
|
532
|
+
let basenameCache = this.textRecordCache.get(basename)
|
|
533
|
+
if (!basenameCache) {
|
|
534
|
+
console.log(`📝 Creating new text record cache for basename: ${basename}`)
|
|
535
|
+
basenameCache = new Map()
|
|
536
|
+
this.textRecordCache.set(basename, basenameCache)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
console.log(
|
|
540
|
+
`💾 Caching text record "${key}" = "${value}" for basename: ${basename}`
|
|
541
|
+
)
|
|
542
|
+
basenameCache.set(key, {
|
|
543
|
+
value,
|
|
544
|
+
timestamp: Date.now()
|
|
545
|
+
})
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Clear all caches
|
|
550
|
+
*/
|
|
551
|
+
clearCache(): void {
|
|
552
|
+
const basenameCount = this.cache.size
|
|
553
|
+
const textRecordCount = this.textRecordCache.size
|
|
554
|
+
|
|
555
|
+
this.cache.clear()
|
|
556
|
+
this.textRecordCache.clear()
|
|
557
|
+
|
|
558
|
+
console.log(`🗑️ Basename cache cleared (${basenameCount} entries removed)`)
|
|
559
|
+
console.log(
|
|
560
|
+
`🗑️ Text record cache cleared (${textRecordCount} basename caches removed)`
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Get cache statistics
|
|
566
|
+
*/
|
|
567
|
+
getCacheStats(): {
|
|
568
|
+
basenameCache: { size: number; maxSize: number }
|
|
569
|
+
textRecordCache: { size: number }
|
|
570
|
+
chainId: number | null
|
|
571
|
+
resolverAddress: Address | null
|
|
572
|
+
} {
|
|
573
|
+
return {
|
|
574
|
+
basenameCache: {
|
|
575
|
+
size: this.cache.size,
|
|
576
|
+
maxSize: this.maxCacheSize
|
|
577
|
+
},
|
|
578
|
+
textRecordCache: {
|
|
579
|
+
size: this.textRecordCache.size
|
|
580
|
+
},
|
|
581
|
+
chainId: this.chainId,
|
|
582
|
+
resolverAddress: this.resolverAddress
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|