@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.
@@ -0,0 +1,221 @@
1
+ import type { XmtpClient } from "../types"
2
+
3
+ interface AddressResolverOptions {
4
+ /**
5
+ * Maximum number of addresses to cache
6
+ * @default 1000
7
+ */
8
+ maxCacheSize?: number
9
+ /**
10
+ * Cache TTL in milliseconds
11
+ * @default 86400000 (24 hours)
12
+ */
13
+ cacheTtl?: number
14
+ }
15
+
16
+ interface CacheEntry {
17
+ address: string
18
+ timestamp: number
19
+ }
20
+
21
+ export class AddressResolver {
22
+ private cache = new Map<string, CacheEntry>()
23
+ private readonly maxCacheSize: number
24
+ private readonly cacheTtl: number
25
+
26
+ constructor(
27
+ private client: XmtpClient,
28
+ options: AddressResolverOptions = {}
29
+ ) {
30
+ this.maxCacheSize = options.maxCacheSize ?? 1000
31
+ this.cacheTtl = options.cacheTtl ?? 86400000 // 24 hours
32
+ }
33
+
34
+ /**
35
+ * Resolve user address from inbox ID with caching
36
+ */
37
+ async resolveAddress(
38
+ inboxId: string,
39
+ conversationId?: string
40
+ ): Promise<`0x${string}` | null> {
41
+ // Check cache first (fastest)
42
+ const cached = this.getCachedAddress(inboxId)
43
+ if (cached) {
44
+ console.log(`✅ Resolved user address from cache: ${cached}`)
45
+ return cached
46
+ }
47
+
48
+ let userAddress = undefined
49
+
50
+ try {
51
+ // Try conversation members lookup first (faster than network call)
52
+ if (conversationId) {
53
+ const conversation =
54
+ await this.client.conversations.getConversationById(conversationId)
55
+ if (conversation) {
56
+ userAddress = await this.resolveFromConversation(
57
+ conversation,
58
+ inboxId
59
+ )
60
+ if (userAddress) {
61
+ this.setCachedAddress(inboxId, userAddress)
62
+ console.log(`✅ Resolved user address: ${userAddress}`)
63
+ return userAddress
64
+ }
65
+ }
66
+ }
67
+
68
+ // Fallback to inboxStateFromInboxIds
69
+ userAddress = await this.resolveFromInboxState(inboxId)
70
+ if (userAddress) {
71
+ this.setCachedAddress(inboxId, userAddress)
72
+ console.log(`✅ Resolved user address via fallback: ${userAddress}`)
73
+ return userAddress
74
+ }
75
+
76
+ console.log(`⚠️ No identifiers found for inbox ${inboxId}`)
77
+ return null
78
+ } catch (error) {
79
+ console.error(`❌ Error resolving user address for ${inboxId}:`, error)
80
+ return null
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Resolve address from conversation members
86
+ */
87
+ private async resolveFromConversation(
88
+ conversation: any,
89
+ inboxId: string
90
+ ): Promise<`0x${string}` | null> {
91
+ try {
92
+ const members = await conversation.members()
93
+ const sender = members.find(
94
+ (member: any) => member.inboxId.toLowerCase() === inboxId.toLowerCase()
95
+ )
96
+
97
+ if (sender) {
98
+ const ethIdentifier = sender.accountIdentifiers.find(
99
+ (id: any) => id.identifierKind === 0 // IdentifierKind.Ethereum
100
+ )
101
+ if (ethIdentifier) {
102
+ return ethIdentifier.identifier
103
+ } else {
104
+ console.log(`⚠️ No Ethereum identifier found for inbox ${inboxId}`)
105
+ }
106
+ } else {
107
+ console.log(
108
+ `⚠️ Sender not found in conversation members for inbox ${inboxId}`
109
+ )
110
+ }
111
+ } catch (error) {
112
+ console.error(`❌ Error resolving from conversation members:`, error)
113
+ }
114
+
115
+ return null
116
+ }
117
+
118
+ /**
119
+ * Resolve address from inbox state (network fallback)
120
+ */
121
+ private async resolveFromInboxState(inboxId: string): Promise<`0x${string}` | null> {
122
+ try {
123
+ const inboxState = await this.client.preferences.inboxStateFromInboxIds([
124
+ inboxId
125
+ ])
126
+ const firstState = inboxState?.[0]
127
+ if (firstState?.identifiers && firstState.identifiers.length > 0) {
128
+ const firstIdentifier = firstState.identifiers[0]
129
+ return firstIdentifier?.identifier as `0x${string}`
130
+ }
131
+ } catch (error) {
132
+ console.error(`❌ Error resolving from inbox state:`, error)
133
+ }
134
+
135
+ return null
136
+ }
137
+
138
+ /**
139
+ * Get cached address if not expired
140
+ */
141
+ private getCachedAddress(inboxId: string): `0x${string}` | null {
142
+ const entry = this.cache.get(inboxId)
143
+ if (!entry) return null
144
+
145
+ const now = Date.now()
146
+ if (now - entry.timestamp > this.cacheTtl) {
147
+ this.cache.delete(inboxId)
148
+ return null
149
+ }
150
+
151
+ return entry.address as `0x${string}`
152
+ }
153
+
154
+ /**
155
+ * Cache address with LRU eviction
156
+ */
157
+ private setCachedAddress(inboxId: string, address: `0x${string}`): void {
158
+ // Simple LRU: if cache is full, remove oldest entry
159
+ if (this.cache.size >= this.maxCacheSize) {
160
+ const firstKey = this.cache.keys().next().value
161
+ if (firstKey) {
162
+ this.cache.delete(firstKey)
163
+ }
164
+ }
165
+
166
+ this.cache.set(inboxId, {
167
+ address,
168
+ timestamp: Date.now()
169
+ })
170
+ }
171
+
172
+ /**
173
+ * Pre-populate cache from existing conversations
174
+ */
175
+ async prePopulateCache(): Promise<void> {
176
+ console.log("🔄 Pre-populating address cache...")
177
+ try {
178
+ const conversations = await this.client.conversations.list()
179
+ let cachedCount = 0
180
+
181
+ for (const conversation of conversations) {
182
+ try {
183
+ const members = await conversation.members()
184
+ for (const member of members) {
185
+ const ethIdentifier = member.accountIdentifiers.find(
186
+ (id: any) => id.identifierKind === 0 // IdentifierKind.Ethereum
187
+ )
188
+ if (ethIdentifier) {
189
+ this.setCachedAddress(member.inboxId, ethIdentifier.identifier as `0x${string}`)
190
+ cachedCount++
191
+ }
192
+ }
193
+ } catch (error) {
194
+ console.error("Error pre-caching conversation members:", error)
195
+ }
196
+ }
197
+
198
+ console.log(`✅ Pre-cached ${cachedCount} address mappings`)
199
+ } catch (error) {
200
+ console.error("Error pre-populating cache:", error)
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Clear the cache
206
+ */
207
+ clearCache(): void {
208
+ this.cache.clear()
209
+ console.log("🗑️ Address cache cleared")
210
+ }
211
+
212
+ /**
213
+ * Get cache statistics
214
+ */
215
+ getCacheStats(): { size: number; maxSize: number; hitRate?: number } {
216
+ return {
217
+ size: this.cache.size,
218
+ maxSize: this.maxCacheSize
219
+ }
220
+ }
221
+ }