@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,324 @@
|
|
|
1
|
+
import { type Address, PublicClient } from "viem"
|
|
2
|
+
|
|
3
|
+
interface ENSResolverOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Maximum number of ENS names to cache
|
|
6
|
+
* @default 500
|
|
7
|
+
*/
|
|
8
|
+
maxCacheSize?: number
|
|
9
|
+
/**
|
|
10
|
+
* Cache TTL in milliseconds
|
|
11
|
+
* @default 3600000 (1 hour)
|
|
12
|
+
*/
|
|
13
|
+
cacheTtl?: number
|
|
14
|
+
/**
|
|
15
|
+
* Mainnet public client for ENS resolution
|
|
16
|
+
*/
|
|
17
|
+
mainnetClient: PublicClient
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface CacheEntry {
|
|
21
|
+
address: string
|
|
22
|
+
timestamp: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ReverseCacheEntry {
|
|
26
|
+
ensName: string
|
|
27
|
+
timestamp: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* ENS Resolver for mainnet .eth names
|
|
32
|
+
* Handles resolution of ENS names to addresses and reverse resolution
|
|
33
|
+
*/
|
|
34
|
+
export class ENSResolver {
|
|
35
|
+
private cache = new Map<string, CacheEntry>()
|
|
36
|
+
private reverseCache = new Map<string, ReverseCacheEntry>()
|
|
37
|
+
private readonly maxCacheSize: number
|
|
38
|
+
private readonly cacheTtl: number
|
|
39
|
+
private readonly mainnetClient: PublicClient
|
|
40
|
+
|
|
41
|
+
constructor(options: ENSResolverOptions) {
|
|
42
|
+
this.maxCacheSize = options.maxCacheSize ?? 500
|
|
43
|
+
this.cacheTtl = options.cacheTtl ?? 3600000 // 1 hour
|
|
44
|
+
this.mainnetClient = options.mainnetClient
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve an ENS name to an Ethereum address
|
|
49
|
+
*/
|
|
50
|
+
async resolveENSName(ensName: string): Promise<Address | null> {
|
|
51
|
+
console.log(`🔍 Resolving ENS name: ${ensName}`)
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Check cache first
|
|
55
|
+
const cached = this.getCachedAddress(ensName)
|
|
56
|
+
if (cached) {
|
|
57
|
+
console.log(`✅ Resolved ENS from cache: ${ensName} → ${cached}`)
|
|
58
|
+
return cached as Address
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(`📭 No cached address found for ENS: ${ensName}`)
|
|
62
|
+
|
|
63
|
+
// Resolve using mainnet ENS
|
|
64
|
+
console.log("🔄 Reading ENS contract...")
|
|
65
|
+
const address = await this.mainnetClient.getEnsAddress({
|
|
66
|
+
name: ensName
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
console.log(`📋 ENS contract returned address: "${address}"`)
|
|
70
|
+
|
|
71
|
+
if (address && address !== "0x0000000000000000000000000000000000000000") {
|
|
72
|
+
this.setCachedAddress(ensName, address)
|
|
73
|
+
console.log(`✅ Resolved ENS: ${ensName} → ${address}`)
|
|
74
|
+
return address
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(`❌ No address found for ENS: ${ensName}`)
|
|
78
|
+
return null
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`❌ Error resolving ENS name ${ensName}:`, error)
|
|
81
|
+
if (error instanceof Error) {
|
|
82
|
+
console.error(`❌ Error details: ${error.message}`)
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve an address to its primary ENS name (reverse resolution)
|
|
90
|
+
*/
|
|
91
|
+
async resolveAddressToENS(address: Address): Promise<string | null> {
|
|
92
|
+
console.log(`🔍 Reverse resolving address to ENS: ${address}`)
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// Check cache first
|
|
96
|
+
const cached = this.getCachedENSName(address)
|
|
97
|
+
if (cached) {
|
|
98
|
+
console.log(
|
|
99
|
+
`✅ Resolved ENS from reverse cache: ${address} → ${cached}`
|
|
100
|
+
)
|
|
101
|
+
return cached
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`📭 No cached ENS name found for address: ${address}`)
|
|
105
|
+
|
|
106
|
+
// Reverse resolve using mainnet ENS
|
|
107
|
+
console.log("🔄 Reading ENS reverse resolver...")
|
|
108
|
+
const ensName = await this.mainnetClient.getEnsName({
|
|
109
|
+
address: address
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
console.log(`📋 ENS reverse resolver returned: "${ensName}"`)
|
|
113
|
+
|
|
114
|
+
if (ensName && ensName.length > 0) {
|
|
115
|
+
this.setCachedENSName(address, ensName)
|
|
116
|
+
console.log(`✅ Reverse resolved: ${address} → ${ensName}`)
|
|
117
|
+
return ensName
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(`❌ No ENS name found for address: ${address}`)
|
|
121
|
+
return null
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error(`❌ Error reverse resolving address ${address}:`, error)
|
|
124
|
+
if (error instanceof Error) {
|
|
125
|
+
console.error(`❌ Error details: ${error.message}`)
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get ENS avatar for a name
|
|
133
|
+
*/
|
|
134
|
+
async getENSAvatar(ensName: string): Promise<string | null> {
|
|
135
|
+
console.log(`🖼️ Getting ENS avatar for: ${ensName}`)
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const avatar = await this.mainnetClient.getEnsAvatar({
|
|
139
|
+
name: ensName
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (avatar) {
|
|
143
|
+
console.log(`✅ Found ENS avatar: ${avatar}`)
|
|
144
|
+
return avatar
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`❌ No avatar found for ENS: ${ensName}`)
|
|
148
|
+
return null
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(`❌ Error getting ENS avatar for ${ensName}:`, error)
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get ENS text record
|
|
157
|
+
*/
|
|
158
|
+
async getENSTextRecord(ensName: string, key: string): Promise<string | null> {
|
|
159
|
+
console.log(`📝 Getting ENS text record "${key}" for: ${ensName}`)
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const textRecord = await this.mainnetClient.getEnsText({
|
|
163
|
+
name: ensName,
|
|
164
|
+
key: key
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
if (textRecord && textRecord.length > 0) {
|
|
168
|
+
console.log(`✅ Found ENS text record: ${key}=${textRecord}`)
|
|
169
|
+
return textRecord
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(`❌ No text record "${key}" found for ENS: ${ensName}`)
|
|
173
|
+
return null
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(
|
|
176
|
+
`❌ Error getting ENS text record ${key} for ${ensName}:`,
|
|
177
|
+
error
|
|
178
|
+
)
|
|
179
|
+
return null
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get comprehensive ENS profile
|
|
185
|
+
*/
|
|
186
|
+
async getENSProfile(ensName: string) {
|
|
187
|
+
console.log(`👤 Getting ENS profile for: ${ensName}`)
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const [address, avatar, description, twitter, github, url] =
|
|
191
|
+
await Promise.all([
|
|
192
|
+
this.resolveENSName(ensName),
|
|
193
|
+
this.getENSAvatar(ensName),
|
|
194
|
+
this.getENSTextRecord(ensName, "description"),
|
|
195
|
+
this.getENSTextRecord(ensName, "com.twitter"),
|
|
196
|
+
this.getENSTextRecord(ensName, "com.github"),
|
|
197
|
+
this.getENSTextRecord(ensName, "url")
|
|
198
|
+
])
|
|
199
|
+
|
|
200
|
+
const profile = {
|
|
201
|
+
ensName,
|
|
202
|
+
address,
|
|
203
|
+
avatar,
|
|
204
|
+
description,
|
|
205
|
+
twitter,
|
|
206
|
+
github,
|
|
207
|
+
url
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`✅ ENS profile for ${ensName}:`, profile)
|
|
211
|
+
return profile
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(`❌ Error getting ENS profile for ${ensName}:`, error)
|
|
214
|
+
return null
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check if a name is a valid ENS name (.eth)
|
|
220
|
+
*/
|
|
221
|
+
isENSName(name: string): boolean {
|
|
222
|
+
return name.endsWith(".eth") && !name.endsWith(".base.eth")
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get cached address if not expired
|
|
227
|
+
*/
|
|
228
|
+
private getCachedAddress(ensName: string): string | null {
|
|
229
|
+
const entry = this.cache.get(ensName.toLowerCase())
|
|
230
|
+
if (!entry) {
|
|
231
|
+
return null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const now = Date.now()
|
|
235
|
+
if (now - entry.timestamp > this.cacheTtl) {
|
|
236
|
+
this.cache.delete(ensName.toLowerCase())
|
|
237
|
+
return null
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return entry.address
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Cache address with LRU eviction
|
|
245
|
+
*/
|
|
246
|
+
private setCachedAddress(ensName: string, address: string): void {
|
|
247
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
248
|
+
const firstKey = this.cache.keys().next().value
|
|
249
|
+
if (firstKey) {
|
|
250
|
+
this.cache.delete(firstKey)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this.cache.set(ensName.toLowerCase(), {
|
|
255
|
+
address,
|
|
256
|
+
timestamp: Date.now()
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get cached ENS name if not expired
|
|
262
|
+
*/
|
|
263
|
+
private getCachedENSName(address: Address): string | null {
|
|
264
|
+
const entry = this.reverseCache.get(address.toLowerCase())
|
|
265
|
+
if (!entry) {
|
|
266
|
+
return null
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const now = Date.now()
|
|
270
|
+
if (now - entry.timestamp > this.cacheTtl) {
|
|
271
|
+
this.reverseCache.delete(address.toLowerCase())
|
|
272
|
+
return null
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return entry.ensName
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Cache ENS name with LRU eviction
|
|
280
|
+
*/
|
|
281
|
+
private setCachedENSName(address: Address, ensName: string): void {
|
|
282
|
+
if (this.reverseCache.size >= this.maxCacheSize) {
|
|
283
|
+
const firstKey = this.reverseCache.keys().next().value
|
|
284
|
+
if (firstKey) {
|
|
285
|
+
this.reverseCache.delete(firstKey)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.reverseCache.set(address.toLowerCase(), {
|
|
290
|
+
ensName,
|
|
291
|
+
timestamp: Date.now()
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Clear all caches
|
|
297
|
+
*/
|
|
298
|
+
clearCache(): void {
|
|
299
|
+
const addressCount = this.cache.size
|
|
300
|
+
const ensCount = this.reverseCache.size
|
|
301
|
+
|
|
302
|
+
this.cache.clear()
|
|
303
|
+
this.reverseCache.clear()
|
|
304
|
+
|
|
305
|
+
console.log(`🗑️ ENS address cache cleared (${addressCount} entries removed)`)
|
|
306
|
+
console.log(`🗑️ ENS reverse cache cleared (${ensCount} entries removed)`)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get cache statistics
|
|
311
|
+
*/
|
|
312
|
+
getCacheStats() {
|
|
313
|
+
return {
|
|
314
|
+
addressCache: {
|
|
315
|
+
size: this.cache.size,
|
|
316
|
+
maxSize: this.maxCacheSize
|
|
317
|
+
},
|
|
318
|
+
reverseCache: {
|
|
319
|
+
size: this.reverseCache.size,
|
|
320
|
+
maxSize: this.maxCacheSize
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./resolver"
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { type Address, type PublicClient } from "viem"
|
|
2
|
+
import type { XmtpClient, XmtpMessage, XmtpSender } from "../types"
|
|
3
|
+
import { AddressResolver } from "./address-resolver"
|
|
4
|
+
import {
|
|
5
|
+
type BaseName,
|
|
6
|
+
BasenameResolver,
|
|
7
|
+
type BasenameTextRecordKey
|
|
8
|
+
} from "./basename-resolver"
|
|
9
|
+
import { ENSResolver } from "./ens-resolver"
|
|
10
|
+
import { XmtpResolver } from "./xmtp-resolver"
|
|
11
|
+
|
|
12
|
+
interface ResolverOptions {
|
|
13
|
+
/**
|
|
14
|
+
* XMTP Client for message and address resolution
|
|
15
|
+
*/
|
|
16
|
+
xmtpClient: XmtpClient
|
|
17
|
+
/**
|
|
18
|
+
* Mainnet public client for ENS resolution
|
|
19
|
+
*/
|
|
20
|
+
mainnetClient: PublicClient
|
|
21
|
+
/**
|
|
22
|
+
* Base network public client for basename resolution
|
|
23
|
+
*/
|
|
24
|
+
baseClient: PublicClient
|
|
25
|
+
/**
|
|
26
|
+
* Maximum cache size for each resolver
|
|
27
|
+
* @default 1000
|
|
28
|
+
*/
|
|
29
|
+
maxCacheSize?: number
|
|
30
|
+
/**
|
|
31
|
+
* Cache TTL in milliseconds
|
|
32
|
+
* @default 3600000 (1 hour)
|
|
33
|
+
*/
|
|
34
|
+
cacheTtl?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Master Resolver that wraps all individual resolvers
|
|
39
|
+
* Provides a unified interface for basename, ENS, address, and XMTP resolution
|
|
40
|
+
*/
|
|
41
|
+
export class Resolver {
|
|
42
|
+
private addressResolver: AddressResolver
|
|
43
|
+
private ensResolver: ENSResolver
|
|
44
|
+
private basenameResolver: BasenameResolver
|
|
45
|
+
private xmtpResolver: XmtpResolver
|
|
46
|
+
|
|
47
|
+
constructor(options: ResolverOptions) {
|
|
48
|
+
const resolverOptions = {
|
|
49
|
+
maxCacheSize: options.maxCacheSize ?? 1000,
|
|
50
|
+
cacheTtl: options.cacheTtl ?? 3600000
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.addressResolver = new AddressResolver(
|
|
54
|
+
options.xmtpClient,
|
|
55
|
+
resolverOptions
|
|
56
|
+
)
|
|
57
|
+
this.xmtpResolver = new XmtpResolver(options.xmtpClient, resolverOptions)
|
|
58
|
+
|
|
59
|
+
// Type assertions needed due to viem version differences across monorepo packages
|
|
60
|
+
// Both clients are PublicClient-compatible but TypeScript sees them as incompatible types
|
|
61
|
+
this.ensResolver = new ENSResolver({
|
|
62
|
+
...resolverOptions,
|
|
63
|
+
mainnetClient: options.mainnetClient as PublicClient
|
|
64
|
+
})
|
|
65
|
+
this.basenameResolver = new BasenameResolver({
|
|
66
|
+
...resolverOptions,
|
|
67
|
+
publicClient: options.baseClient as PublicClient
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// === Address Resolution Methods ===
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolve user address from inbox ID with caching
|
|
75
|
+
* Uses both AddressResolver and XmtpResolver for redundancy
|
|
76
|
+
*/
|
|
77
|
+
async resolveAddress(
|
|
78
|
+
inboxId: string,
|
|
79
|
+
conversationId?: string
|
|
80
|
+
): Promise<`0x${string}` | null> {
|
|
81
|
+
// Try AddressResolver first, fallback to XmtpResolver
|
|
82
|
+
let result = await this.addressResolver.resolveAddress(
|
|
83
|
+
inboxId,
|
|
84
|
+
conversationId
|
|
85
|
+
)
|
|
86
|
+
if (!result) {
|
|
87
|
+
result = await this.xmtpResolver.resolveAddress(inboxId, conversationId)
|
|
88
|
+
}
|
|
89
|
+
return result
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// === ENS Resolution Methods ===
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolve an ENS name to an Ethereum address
|
|
96
|
+
*/
|
|
97
|
+
async resolveENSName(ensName: string): Promise<Address | null> {
|
|
98
|
+
return this.ensResolver.resolveENSName(ensName)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve an address to its primary ENS name (reverse resolution)
|
|
103
|
+
*/
|
|
104
|
+
async resolveAddressToENS(address: Address): Promise<string | null> {
|
|
105
|
+
return this.ensResolver.resolveAddressToENS(address)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get ENS avatar for a given ENS name
|
|
110
|
+
*/
|
|
111
|
+
async getENSAvatar(ensName: string): Promise<string | null> {
|
|
112
|
+
return this.ensResolver.getENSAvatar(ensName)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get ENS text record for a given ENS name and key
|
|
117
|
+
*/
|
|
118
|
+
async getENSTextRecord(ensName: string, key: string): Promise<string | null> {
|
|
119
|
+
return this.ensResolver.getENSTextRecord(ensName, key)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get complete ENS profile for a given ENS name
|
|
124
|
+
*/
|
|
125
|
+
async getENSProfile(ensName: string) {
|
|
126
|
+
return this.ensResolver.getENSProfile(ensName)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// === Basename Resolution Methods ===
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get basename from an Ethereum address
|
|
133
|
+
*/
|
|
134
|
+
async getBasename(address: Address): Promise<string | null> {
|
|
135
|
+
return this.basenameResolver.getBasename(address)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get basename avatar for a given basename
|
|
140
|
+
*/
|
|
141
|
+
async getBasenameAvatar(basename: BaseName): Promise<string | null> {
|
|
142
|
+
return this.basenameResolver.getBasenameAvatar(basename)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get basename text record for a given basename and key
|
|
147
|
+
*/
|
|
148
|
+
async getBasenameTextRecord(
|
|
149
|
+
basename: BaseName,
|
|
150
|
+
key: BasenameTextRecordKey
|
|
151
|
+
): Promise<string | null> {
|
|
152
|
+
return this.basenameResolver.getBasenameTextRecord(basename, key)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Resolve basename to an Ethereum address
|
|
157
|
+
*/
|
|
158
|
+
async getBasenameAddress(basename: BaseName): Promise<Address | null> {
|
|
159
|
+
return this.basenameResolver.getBasenameAddress(basename)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get basename metadata for a given basename
|
|
164
|
+
*/
|
|
165
|
+
async getBasenameMetadata(basename: BaseName) {
|
|
166
|
+
return this.basenameResolver.getBasenameMetadata(basename)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get complete basename profile for a given address
|
|
171
|
+
*/
|
|
172
|
+
async resolveBasenameProfile(address: Address) {
|
|
173
|
+
return this.basenameResolver.resolveBasenameProfile(address)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// === XMTP Message Methods ===
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Find any message by ID with caching
|
|
180
|
+
*/
|
|
181
|
+
async findMessage(messageId: string): Promise<XmtpMessage | null> {
|
|
182
|
+
return this.xmtpResolver.findMessage(messageId)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Find root message by ID (traverses reply chain)
|
|
187
|
+
*/
|
|
188
|
+
async findRootMessage(messageId: string): Promise<XmtpMessage | null> {
|
|
189
|
+
return this.xmtpResolver.findRootMessage(messageId)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// === Universal Resolution Methods ===
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Universal name resolution - tries to resolve any name (ENS or basename) to an address
|
|
196
|
+
*/
|
|
197
|
+
async resolveName(name: string): Promise<Address | null> {
|
|
198
|
+
// Try ENS first (more common)
|
|
199
|
+
if (name.endsWith(".eth")) {
|
|
200
|
+
return this.resolveENSName(name)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Try basename
|
|
204
|
+
if (name.endsWith(".base.eth")) {
|
|
205
|
+
return this.getBasenameAddress(name)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// If no TLD, try both
|
|
209
|
+
const ensResult = await this.resolveENSName(name)
|
|
210
|
+
if (ensResult) {
|
|
211
|
+
return ensResult
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return this.getBasenameAddress(name)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Universal reverse resolution - tries to resolve an address to any name (ENS or basename)
|
|
219
|
+
*/
|
|
220
|
+
async resolveAddressToName(address: Address): Promise<string | null> {
|
|
221
|
+
// Try basename first (more relevant for this project)
|
|
222
|
+
const basename = await this.getBasename(address)
|
|
223
|
+
if (basename) {
|
|
224
|
+
return basename
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Try ENS as fallback
|
|
228
|
+
return this.resolveAddressToENS(address)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get complete profile for an address (combines ENS and basename data)
|
|
233
|
+
*/
|
|
234
|
+
async getCompleteProfile(address: Address) {
|
|
235
|
+
const [ensName, basename, ensProfile, basenameProfile] =
|
|
236
|
+
await Promise.allSettled([
|
|
237
|
+
this.resolveAddressToENS(address),
|
|
238
|
+
this.getBasename(address),
|
|
239
|
+
this.resolveAddressToENS(address).then((name) =>
|
|
240
|
+
name ? this.getENSProfile(name) : null
|
|
241
|
+
),
|
|
242
|
+
this.resolveBasenameProfile(address)
|
|
243
|
+
])
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
address,
|
|
247
|
+
ensName: ensName.status === "fulfilled" ? ensName.value : null,
|
|
248
|
+
basename: basename.status === "fulfilled" ? basename.value : null,
|
|
249
|
+
ensProfile: ensProfile.status === "fulfilled" ? ensProfile.value : null,
|
|
250
|
+
basenameProfile:
|
|
251
|
+
basenameProfile.status === "fulfilled" ? basenameProfile.value : null
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// === Cache Management Methods ===
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Pre-populate all resolver caches
|
|
259
|
+
*/
|
|
260
|
+
async prePopulateAllCaches(): Promise<void> {
|
|
261
|
+
await Promise.allSettled([
|
|
262
|
+
this.addressResolver.prePopulateCache(),
|
|
263
|
+
this.xmtpResolver.prePopulateCache()
|
|
264
|
+
])
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Create a complete XmtpSender object from an address or inboxId
|
|
269
|
+
* Uses the resolver to get the best available name and profile information
|
|
270
|
+
*/
|
|
271
|
+
async createXmtpSender(
|
|
272
|
+
addressOrInboxId: string,
|
|
273
|
+
conversationId?: string
|
|
274
|
+
): Promise<XmtpSender> {
|
|
275
|
+
let address: `0x${string}` | null = null
|
|
276
|
+
let inboxId = addressOrInboxId
|
|
277
|
+
|
|
278
|
+
// Check if input looks like an Ethereum address
|
|
279
|
+
if (addressOrInboxId.startsWith("0x") && addressOrInboxId.length === 42) {
|
|
280
|
+
address = addressOrInboxId as `0x${string}`
|
|
281
|
+
// When we have an address, we need to find the actual inboxId
|
|
282
|
+
// For now, use address as fallback but this should be resolved from XMTP
|
|
283
|
+
inboxId = addressOrInboxId // This will be improved when we have proper address->inboxId resolution
|
|
284
|
+
} else {
|
|
285
|
+
// Assume it's an inboxId, try to resolve to address
|
|
286
|
+
address = await this.resolveAddress(addressOrInboxId, conversationId)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Get the best available name using universal resolution
|
|
290
|
+
let name = "Unknown"
|
|
291
|
+
let basename: string | undefined
|
|
292
|
+
|
|
293
|
+
if (address) {
|
|
294
|
+
// Try basename first since that's what we expect for this address
|
|
295
|
+
const basenameResult = await this.getBasename(address)
|
|
296
|
+
console.log(
|
|
297
|
+
`🔍 [RESOLVER] Direct basename lookup for ${address}:`,
|
|
298
|
+
basenameResult
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
// Try to get a human-readable name
|
|
302
|
+
const resolvedName = await this.resolveAddressToName(address)
|
|
303
|
+
console.log(
|
|
304
|
+
`🔍 [RESOLVER] Universal name resolution for ${address}:`,
|
|
305
|
+
resolvedName
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if (resolvedName) {
|
|
309
|
+
name = resolvedName
|
|
310
|
+
// Check if it's a basename specifically
|
|
311
|
+
if (resolvedName.endsWith(".base.eth")) {
|
|
312
|
+
basename = resolvedName
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
// Fallback to shortened address
|
|
316
|
+
name = `${address.slice(0, 6)}...${address.slice(-4)}`
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Always try to get basename even if ENS was found
|
|
320
|
+
if (!basename) {
|
|
321
|
+
const resolvedBasename = await this.getBasename(address)
|
|
322
|
+
basename = resolvedBasename || undefined
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
// No address resolution available, use inboxId
|
|
326
|
+
name = `${inboxId.slice(0, 8)}...${inboxId.slice(-4)}`
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
address: address || addressOrInboxId,
|
|
331
|
+
inboxId,
|
|
332
|
+
name,
|
|
333
|
+
basename
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|