@hybrd/xmtp 1.0.0 → 1.0.3

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.
Files changed (45) hide show
  1. package/.cache/tsbuildinfo.json +1 -1
  2. package/.turbo/turbo-build.log +5 -0
  3. package/.turbo/turbo-lint$colon$fix.log +6 -0
  4. package/dist/scripts/generate-keys.d.ts +1 -0
  5. package/dist/scripts/generate-keys.js +19 -0
  6. package/dist/scripts/refresh-identity.d.ts +1 -0
  7. package/dist/scripts/refresh-identity.js +93 -0
  8. package/dist/scripts/register-wallet.d.ts +1 -0
  9. package/dist/scripts/register-wallet.js +75 -0
  10. package/dist/scripts/revoke-all-installations.d.ts +2 -0
  11. package/dist/scripts/revoke-all-installations.js +68 -0
  12. package/dist/scripts/revoke-installations.d.ts +2 -0
  13. package/dist/scripts/revoke-installations.js +62 -0
  14. package/dist/src/abi/l2_resolver.d.ts +992 -0
  15. package/dist/src/abi/l2_resolver.js +699 -0
  16. package/dist/src/client.d.ts +76 -0
  17. package/dist/src/client.js +709 -0
  18. package/dist/src/constants.d.ts +3 -0
  19. package/dist/src/constants.js +6 -0
  20. package/dist/src/index.d.ts +22 -0
  21. package/dist/src/index.js +46 -0
  22. package/dist/src/lib/message-listener.d.ts +69 -0
  23. package/dist/src/lib/message-listener.js +235 -0
  24. package/dist/src/lib/message-listener.test.d.ts +1 -0
  25. package/dist/src/lib/message-listener.test.js +303 -0
  26. package/dist/src/lib/subjects.d.ts +24 -0
  27. package/dist/src/lib/subjects.js +68 -0
  28. package/dist/src/resolver/address-resolver.d.ts +57 -0
  29. package/dist/src/resolver/address-resolver.js +168 -0
  30. package/dist/src/resolver/basename-resolver.d.ts +134 -0
  31. package/dist/src/resolver/basename-resolver.js +409 -0
  32. package/dist/src/resolver/ens-resolver.d.ts +95 -0
  33. package/dist/src/resolver/ens-resolver.js +249 -0
  34. package/dist/src/resolver/index.d.ts +1 -0
  35. package/dist/src/resolver/index.js +1 -0
  36. package/dist/src/resolver/resolver.d.ts +162 -0
  37. package/dist/src/resolver/resolver.js +238 -0
  38. package/dist/src/resolver/xmtp-resolver.d.ts +95 -0
  39. package/dist/src/resolver/xmtp-resolver.js +297 -0
  40. package/dist/src/service-client.d.ts +77 -0
  41. package/dist/src/service-client.js +198 -0
  42. package/dist/src/types.d.ts +123 -0
  43. package/dist/src/types.js +5 -0
  44. package/package.json +5 -4
  45. package/tsconfig.json +3 -1
@@ -0,0 +1,303 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { MessageListener } from "./message-listener";
4
+ /**
5
+ * Check if content contains any of the supported agent mention patterns
6
+ */
7
+ function hasAgentMention(content) {
8
+ if (!content)
9
+ return false;
10
+ const lowerContent = content.toLowerCase();
11
+ const mentionPatterns = [
12
+ "@agent",
13
+ "@hybrid",
14
+ "@hybrid.base.eth",
15
+ "@hybrid.eth",
16
+ "@agent.eth",
17
+ "@agent.base.eth"
18
+ ];
19
+ return mentionPatterns.some((pattern) => lowerContent.includes(pattern));
20
+ }
21
+ // Mock the XmtpResolver
22
+ vi.mock("./xmtp-resolver", () => ({
23
+ XmtpResolver: vi.fn().mockImplementation(() => ({
24
+ resolveAddress: vi.fn().mockResolvedValue("0x456789abcdef"),
25
+ findRootMessage: vi.fn().mockResolvedValue(null),
26
+ prePopulateCache: vi.fn().mockResolvedValue(undefined)
27
+ }))
28
+ }));
29
+ // Mock the BasenameResolver
30
+ vi.mock("./basename-resolver", () => ({
31
+ BasenameResolver: vi.fn().mockImplementation(() => ({
32
+ getBasename: vi.fn().mockResolvedValue("testuser.base.eth"),
33
+ getBasenameAddress: vi.fn().mockResolvedValue("0x456789abcdef"),
34
+ resolveBasenameProfile: vi.fn().mockResolvedValue({
35
+ basename: "testuser.base.eth",
36
+ avatar: "https://example.com/avatar.jpg",
37
+ description: "Test user profile",
38
+ twitter: "@testuser",
39
+ github: "testuser",
40
+ url: "https://testuser.com"
41
+ })
42
+ }))
43
+ }));
44
+ // Mock the ENSResolver
45
+ vi.mock("./ens-resolver", () => ({
46
+ ENSResolver: vi.fn().mockImplementation(() => ({
47
+ resolveAddressToENS: vi.fn().mockResolvedValue(null),
48
+ resolveENSName: vi.fn().mockResolvedValue(null),
49
+ isENSName: vi.fn().mockReturnValue(false)
50
+ }))
51
+ }));
52
+ // Mock the subjects
53
+ vi.mock("./subjects", () => ({
54
+ extractSubjects: vi.fn().mockResolvedValue({})
55
+ }));
56
+ // Mock the XMTP client
57
+ const mockClient = {
58
+ inboxId: "test-inbox-id",
59
+ accountIdentifier: { identifier: "0x123" },
60
+ conversations: {
61
+ sync: vi.fn(),
62
+ list: vi.fn().mockResolvedValue([]),
63
+ streamAllMessages: vi.fn(),
64
+ getConversationById: vi.fn()
65
+ },
66
+ preferences: {
67
+ inboxStateFromInboxIds: vi.fn()
68
+ }
69
+ };
70
+ describe("MessageListener", () => {
71
+ let listener;
72
+ beforeEach(() => {
73
+ vi.clearAllMocks();
74
+ listener = new MessageListener({
75
+ xmtpClient: mockClient,
76
+ publicClient: {},
77
+ filter: ({ message }) => {
78
+ const content = message.content;
79
+ return hasAgentMention(content);
80
+ }
81
+ });
82
+ });
83
+ it("should be an instance of EventEmitter", () => {
84
+ expect(listener).toBeInstanceOf(EventEmitter);
85
+ });
86
+ it("should emit message events with enriched sender information", async () => {
87
+ const mockMessage = {
88
+ id: "test-message-id",
89
+ content: "@agent test message",
90
+ senderInboxId: "sender-inbox-id",
91
+ conversationId: "conversation-id",
92
+ sentAt: new Date(),
93
+ contentType: { typeId: "text" }
94
+ };
95
+ // Mock the stream to emit our test message
96
+ const mockStream = {
97
+ async *[Symbol.asyncIterator]() {
98
+ yield mockMessage;
99
+ }
100
+ };
101
+ mockClient.conversations.streamAllMessages.mockResolvedValue(mockStream);
102
+ mockClient.conversations.getConversationById.mockResolvedValue({
103
+ id: "conversation-id"
104
+ });
105
+ // Set up message event listener
106
+ const messageHandler = vi.fn();
107
+ listener.on("message", messageHandler);
108
+ // Start the listener (but don't wait for it to complete since it runs indefinitely)
109
+ const startPromise = listener.start();
110
+ // Give it a moment to process the message
111
+ await new Promise((resolve) => setTimeout(resolve, 100));
112
+ expect(messageHandler).toHaveBeenCalledWith(expect.objectContaining({
113
+ message: expect.objectContaining({
114
+ id: "test-message-id",
115
+ content: "@agent test message",
116
+ senderInboxId: "sender-inbox-id",
117
+ conversationId: "conversation-id"
118
+ }),
119
+ sender: expect.objectContaining({
120
+ address: "0x456789abcdef",
121
+ inboxId: "sender-inbox-id",
122
+ basename: "testuser.base.eth",
123
+ name: "testuser.base.eth"
124
+ }),
125
+ subjects: expect.any(Object),
126
+ rootMessage: undefined
127
+ }));
128
+ listener.stop();
129
+ });
130
+ it("should handle messages without basenames gracefully", async () => {
131
+ // Mock resolvers to return no basename
132
+ const listenerWithoutBasename = new MessageListener({
133
+ xmtpClient: mockClient,
134
+ publicClient: {},
135
+ filter: ({ message }) => {
136
+ const content = message.content;
137
+ return hasAgentMention(content);
138
+ }
139
+ });
140
+ // Mock basename resolver to return null
141
+ const mockBasenameResolver = listenerWithoutBasename
142
+ .basenameResolver;
143
+ mockBasenameResolver.getBasename = vi.fn().mockResolvedValue(null);
144
+ const mockMessage = {
145
+ id: "test-message-id-2",
146
+ content: "@agent test message 2",
147
+ senderInboxId: "sender-inbox-id-2",
148
+ conversationId: "conversation-id-2",
149
+ sentAt: new Date(),
150
+ contentType: { typeId: "text" }
151
+ };
152
+ const mockStream = {
153
+ async *[Symbol.asyncIterator]() {
154
+ yield mockMessage;
155
+ }
156
+ };
157
+ mockClient.conversations.streamAllMessages.mockResolvedValue(mockStream);
158
+ mockClient.conversations.getConversationById.mockResolvedValue({
159
+ id: "conversation-id-2"
160
+ });
161
+ const messageHandler = vi.fn();
162
+ listenerWithoutBasename.on("message", messageHandler);
163
+ const startPromise = listenerWithoutBasename.start();
164
+ await new Promise((resolve) => setTimeout(resolve, 100));
165
+ expect(messageHandler).toHaveBeenCalledWith(expect.objectContaining({
166
+ sender: expect.objectContaining({
167
+ address: "0x456789abcdef",
168
+ inboxId: "sender-inbox-id-2",
169
+ basename: undefined,
170
+ name: expect.stringContaining("0x4567") // Should use truncated address
171
+ }),
172
+ subjects: expect.any(Object),
173
+ rootMessage: undefined
174
+ }));
175
+ listenerWithoutBasename.stop();
176
+ });
177
+ it("should handle all supported agent mention patterns", async () => {
178
+ const mentionPatterns = [
179
+ "@agent test message",
180
+ "@hybrid test message",
181
+ "@hybrid.base.eth test message",
182
+ "@hybrid.eth test message",
183
+ "@agent.eth test message",
184
+ "@agent.base.eth test message"
185
+ ];
186
+ for (const content of mentionPatterns) {
187
+ const testListener = new MessageListener({
188
+ xmtpClient: mockClient,
189
+ publicClient: {},
190
+ filter: ({ message }) => {
191
+ const messageContent = message.content;
192
+ return hasAgentMention(messageContent);
193
+ }
194
+ });
195
+ const mockMessage = {
196
+ id: `test-message-${content}`,
197
+ content: content,
198
+ senderInboxId: "sender-inbox-id",
199
+ conversationId: "conversation-id",
200
+ sentAt: new Date(),
201
+ contentType: { typeId: "text" }
202
+ };
203
+ const mockStream = {
204
+ async *[Symbol.asyncIterator]() {
205
+ yield mockMessage;
206
+ }
207
+ };
208
+ mockClient.conversations.streamAllMessages.mockResolvedValue(mockStream);
209
+ mockClient.conversations.getConversationById.mockResolvedValue({
210
+ id: "conversation-id"
211
+ });
212
+ const messageHandler = vi.fn();
213
+ testListener.on("message", messageHandler);
214
+ const startPromise = testListener.start();
215
+ await new Promise((resolve) => setTimeout(resolve, 100));
216
+ expect(messageHandler).toHaveBeenCalledWith(expect.objectContaining({
217
+ message: expect.objectContaining({
218
+ content: content
219
+ })
220
+ }));
221
+ testListener.stop();
222
+ }
223
+ });
224
+ it("should allow replies without mention checking", async () => {
225
+ const testListener = new MessageListener({
226
+ xmtpClient: mockClient,
227
+ publicClient: {},
228
+ filter: ({ message }) => {
229
+ const contentTypeId = message.contentType?.typeId;
230
+ if (contentTypeId === "reply") {
231
+ return true;
232
+ }
233
+ if (contentTypeId === "text") {
234
+ const messageContent = message.content;
235
+ return hasAgentMention(messageContent);
236
+ }
237
+ return false;
238
+ }
239
+ });
240
+ const mockReplyMessage = {
241
+ id: "test-reply-message",
242
+ content: { content: "yes I'm in" },
243
+ senderInboxId: "sender-inbox-id",
244
+ conversationId: "conversation-id",
245
+ sentAt: new Date(),
246
+ contentType: { typeId: "reply" }
247
+ };
248
+ const mockStream = {
249
+ async *[Symbol.asyncIterator]() {
250
+ yield mockReplyMessage;
251
+ }
252
+ };
253
+ mockClient.conversations.streamAllMessages.mockResolvedValue(mockStream);
254
+ mockClient.conversations.getConversationById.mockResolvedValue({
255
+ id: "conversation-id"
256
+ });
257
+ const messageHandler = vi.fn();
258
+ testListener.on("message", messageHandler);
259
+ const startPromise = testListener.start();
260
+ await new Promise((resolve) => setTimeout(resolve, 100));
261
+ expect(messageHandler).toHaveBeenCalledWith(expect.objectContaining({
262
+ message: expect.objectContaining({
263
+ content: expect.objectContaining({
264
+ content: "yes I'm in"
265
+ })
266
+ })
267
+ }));
268
+ testListener.stop();
269
+ });
270
+ it("should properly clean up when stopped", () => {
271
+ const removeAllListenersSpy = vi.spyOn(listener, "removeAllListeners");
272
+ listener.stop();
273
+ expect(removeAllListenersSpy).toHaveBeenCalled();
274
+ });
275
+ it("should get stats", () => {
276
+ const stats = listener.getStats();
277
+ expect(stats).toEqual({
278
+ messageCount: 0,
279
+ conversationCount: 0,
280
+ isActive: false
281
+ });
282
+ });
283
+ it("should emit started and stopped events", async () => {
284
+ const startedHandler = vi.fn();
285
+ const stoppedHandler = vi.fn();
286
+ listener.on("started", startedHandler);
287
+ listener.on("stopped", stoppedHandler);
288
+ // Mock to prevent infinite stream
289
+ mockClient.conversations.streamAllMessages.mockResolvedValue({
290
+ async *[Symbol.asyncIterator]() {
291
+ // Empty iterator that ends immediately
292
+ }
293
+ });
294
+ await listener.start();
295
+ // Give a moment for the events to be processed
296
+ await new Promise((resolve) => setTimeout(resolve, 10));
297
+ expect(startedHandler).toHaveBeenCalled();
298
+ listener.stop();
299
+ // Give a moment for the stop event to be processed
300
+ await new Promise((resolve) => setTimeout(resolve, 10));
301
+ expect(stoppedHandler).toHaveBeenCalled();
302
+ });
303
+ });
@@ -0,0 +1,24 @@
1
+ import type { BasenameResolver } from "../resolver/basename-resolver";
2
+ import type { ENSResolver } from "../resolver/ens-resolver";
3
+ /**
4
+ * Extract basenames/ENS names from message content using @mention pattern
5
+ * @param content The message content to parse
6
+ * @returns Array of unique names found in the message
7
+ */
8
+ export declare function extractMentionedNames(content: string): string[];
9
+ /**
10
+ * Resolve mentioned names to addresses and return as subjects object
11
+ * @param mentionedNames Array of names to resolve
12
+ * @param basenameResolver Basename resolver instance
13
+ * @param ensResolver ENS resolver instance
14
+ * @returns Promise that resolves to subjects object mapping names to addresses
15
+ */
16
+ export declare function resolveSubjects(mentionedNames: string[], basenameResolver: BasenameResolver, ensResolver: ENSResolver): Promise<Record<string, `0x${string}`>>;
17
+ /**
18
+ * Extract subjects from message content (combines extraction and resolution)
19
+ * @param content The message content to parse
20
+ * @param basenameResolver Basename resolver instance
21
+ * @param ensResolver ENS resolver instance
22
+ * @returns Promise that resolves to subjects object mapping names to addresses
23
+ */
24
+ export declare function extractSubjects(content: string, basenameResolver: BasenameResolver, ensResolver: ENSResolver): Promise<Record<string, `0x${string}`>>;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Extract basenames/ENS names from message content using @mention pattern
3
+ * @param content The message content to parse
4
+ * @returns Array of unique names found in the message
5
+ */
6
+ export function extractMentionedNames(content) {
7
+ // Match @basename.eth and @basename.base.eth patterns (case insensitive)
8
+ const nameRegex = /@([a-zA-Z0-9-_]+\.(?:base\.)?eth)\b/gi;
9
+ const matches = content.match(nameRegex);
10
+ if (!matches) {
11
+ return [];
12
+ }
13
+ // Remove @ symbol and deduplicate
14
+ const names = matches.map((match) => match.slice(1).toLowerCase());
15
+ return [...new Set(names)];
16
+ }
17
+ /**
18
+ * Resolve mentioned names to addresses and return as subjects object
19
+ * @param mentionedNames Array of names to resolve
20
+ * @param basenameResolver Basename resolver instance
21
+ * @param ensResolver ENS resolver instance
22
+ * @returns Promise that resolves to subjects object mapping names to addresses
23
+ */
24
+ export async function resolveSubjects(mentionedNames, basenameResolver, ensResolver) {
25
+ const subjects = {};
26
+ if (mentionedNames.length === 0) {
27
+ return subjects;
28
+ }
29
+ console.log(`🔍 Found ${mentionedNames.length} name mentions:`, mentionedNames);
30
+ for (const mentionedName of mentionedNames) {
31
+ try {
32
+ let resolvedAddress = null;
33
+ // Check if it's an ENS name (.eth but not .base.eth)
34
+ if (ensResolver.isENSName(mentionedName)) {
35
+ console.log(`🔍 Resolving ENS name: ${mentionedName}`);
36
+ resolvedAddress = await ensResolver.resolveENSName(mentionedName);
37
+ }
38
+ else {
39
+ // It's a basename (.base.eth or other format)
40
+ console.log(`🔍 Resolving basename: ${mentionedName}`);
41
+ resolvedAddress =
42
+ await basenameResolver.getBasenameAddress(mentionedName);
43
+ }
44
+ if (resolvedAddress) {
45
+ subjects[mentionedName] = resolvedAddress;
46
+ console.log(`✅ Resolved ${mentionedName} → ${resolvedAddress}`);
47
+ }
48
+ else {
49
+ console.log(`❌ Could not resolve address for: ${mentionedName}`);
50
+ }
51
+ }
52
+ catch (error) {
53
+ console.error(`❌ Error resolving ${mentionedName}:`, error);
54
+ }
55
+ }
56
+ return subjects;
57
+ }
58
+ /**
59
+ * Extract subjects from message content (combines extraction and resolution)
60
+ * @param content The message content to parse
61
+ * @param basenameResolver Basename resolver instance
62
+ * @param ensResolver ENS resolver instance
63
+ * @returns Promise that resolves to subjects object mapping names to addresses
64
+ */
65
+ export async function extractSubjects(content, basenameResolver, ensResolver) {
66
+ const mentionedNames = extractMentionedNames(content);
67
+ return await resolveSubjects(mentionedNames, basenameResolver, ensResolver);
68
+ }
@@ -0,0 +1,57 @@
1
+ import type { XmtpClient } from "../types";
2
+ interface AddressResolverOptions {
3
+ /**
4
+ * Maximum number of addresses to cache
5
+ * @default 1000
6
+ */
7
+ maxCacheSize?: number;
8
+ /**
9
+ * Cache TTL in milliseconds
10
+ * @default 86400000 (24 hours)
11
+ */
12
+ cacheTtl?: number;
13
+ }
14
+ export declare class AddressResolver {
15
+ private client;
16
+ private cache;
17
+ private readonly maxCacheSize;
18
+ private readonly cacheTtl;
19
+ constructor(client: XmtpClient, options?: AddressResolverOptions);
20
+ /**
21
+ * Resolve user address from inbox ID with caching
22
+ */
23
+ resolveAddress(inboxId: string, conversationId?: string): Promise<`0x${string}` | null>;
24
+ /**
25
+ * Resolve address from conversation members
26
+ */
27
+ private resolveFromConversation;
28
+ /**
29
+ * Resolve address from inbox state (network fallback)
30
+ */
31
+ private resolveFromInboxState;
32
+ /**
33
+ * Get cached address if not expired
34
+ */
35
+ private getCachedAddress;
36
+ /**
37
+ * Cache address with LRU eviction
38
+ */
39
+ private setCachedAddress;
40
+ /**
41
+ * Pre-populate cache from existing conversations
42
+ */
43
+ prePopulateCache(): Promise<void>;
44
+ /**
45
+ * Clear the cache
46
+ */
47
+ clearCache(): void;
48
+ /**
49
+ * Get cache statistics
50
+ */
51
+ getCacheStats(): {
52
+ size: number;
53
+ maxSize: number;
54
+ hitRate?: number;
55
+ };
56
+ }
57
+ export {};
@@ -0,0 +1,168 @@
1
+ export class AddressResolver {
2
+ constructor(client, options = {}) {
3
+ this.client = client;
4
+ this.cache = new Map();
5
+ this.maxCacheSize = options.maxCacheSize ?? 1000;
6
+ this.cacheTtl = options.cacheTtl ?? 86400000; // 24 hours
7
+ }
8
+ /**
9
+ * Resolve user address from inbox ID with caching
10
+ */
11
+ async resolveAddress(inboxId, conversationId) {
12
+ // Check cache first (fastest)
13
+ const cached = this.getCachedAddress(inboxId);
14
+ if (cached) {
15
+ console.log(`✅ Resolved user address from cache: ${cached}`);
16
+ return cached;
17
+ }
18
+ let userAddress = undefined;
19
+ try {
20
+ // Try conversation members lookup first (faster than network call)
21
+ if (conversationId) {
22
+ const conversation = await this.client.conversations.getConversationById(conversationId);
23
+ if (conversation) {
24
+ userAddress = await this.resolveFromConversation(conversation, inboxId);
25
+ if (userAddress) {
26
+ this.setCachedAddress(inboxId, userAddress);
27
+ console.log(`✅ Resolved user address: ${userAddress}`);
28
+ return userAddress;
29
+ }
30
+ }
31
+ }
32
+ // Fallback to inboxStateFromInboxIds
33
+ userAddress = await this.resolveFromInboxState(inboxId);
34
+ if (userAddress) {
35
+ this.setCachedAddress(inboxId, userAddress);
36
+ console.log(`✅ Resolved user address via fallback: ${userAddress}`);
37
+ return userAddress;
38
+ }
39
+ console.log(`⚠️ No identifiers found for inbox ${inboxId}`);
40
+ return null;
41
+ }
42
+ catch (error) {
43
+ console.error(`❌ Error resolving user address for ${inboxId}:`, error);
44
+ return null;
45
+ }
46
+ }
47
+ /**
48
+ * Resolve address from conversation members
49
+ */
50
+ async resolveFromConversation(conversation, inboxId) {
51
+ try {
52
+ const members = await conversation.members();
53
+ const sender = members.find((member) => member.inboxId.toLowerCase() === inboxId.toLowerCase());
54
+ if (sender) {
55
+ const ethIdentifier = sender.accountIdentifiers.find((id) => id.identifierKind === 0 // IdentifierKind.Ethereum
56
+ );
57
+ if (ethIdentifier) {
58
+ return ethIdentifier.identifier;
59
+ }
60
+ else {
61
+ console.log(`⚠️ No Ethereum identifier found for inbox ${inboxId}`);
62
+ }
63
+ }
64
+ else {
65
+ console.log(`⚠️ Sender not found in conversation members for inbox ${inboxId}`);
66
+ }
67
+ }
68
+ catch (error) {
69
+ console.error(`❌ Error resolving from conversation members:`, error);
70
+ }
71
+ return null;
72
+ }
73
+ /**
74
+ * Resolve address from inbox state (network fallback)
75
+ */
76
+ async resolveFromInboxState(inboxId) {
77
+ try {
78
+ const inboxState = await this.client.preferences.inboxStateFromInboxIds([
79
+ inboxId
80
+ ]);
81
+ const firstState = inboxState?.[0];
82
+ if (firstState?.identifiers && firstState.identifiers.length > 0) {
83
+ const firstIdentifier = firstState.identifiers[0];
84
+ return firstIdentifier?.identifier;
85
+ }
86
+ }
87
+ catch (error) {
88
+ console.error(`❌ Error resolving from inbox state:`, error);
89
+ }
90
+ return null;
91
+ }
92
+ /**
93
+ * Get cached address if not expired
94
+ */
95
+ getCachedAddress(inboxId) {
96
+ const entry = this.cache.get(inboxId);
97
+ if (!entry)
98
+ return null;
99
+ const now = Date.now();
100
+ if (now - entry.timestamp > this.cacheTtl) {
101
+ this.cache.delete(inboxId);
102
+ return null;
103
+ }
104
+ return entry.address;
105
+ }
106
+ /**
107
+ * Cache address with LRU eviction
108
+ */
109
+ setCachedAddress(inboxId, address) {
110
+ // Simple LRU: if cache is full, remove oldest entry
111
+ if (this.cache.size >= this.maxCacheSize) {
112
+ const firstKey = this.cache.keys().next().value;
113
+ if (firstKey) {
114
+ this.cache.delete(firstKey);
115
+ }
116
+ }
117
+ this.cache.set(inboxId, {
118
+ address,
119
+ timestamp: Date.now()
120
+ });
121
+ }
122
+ /**
123
+ * Pre-populate cache from existing conversations
124
+ */
125
+ async prePopulateCache() {
126
+ console.log("🔄 Pre-populating address cache...");
127
+ try {
128
+ const conversations = await this.client.conversations.list();
129
+ let cachedCount = 0;
130
+ for (const conversation of conversations) {
131
+ try {
132
+ const members = await conversation.members();
133
+ for (const member of members) {
134
+ const ethIdentifier = member.accountIdentifiers.find((id) => id.identifierKind === 0 // IdentifierKind.Ethereum
135
+ );
136
+ if (ethIdentifier) {
137
+ this.setCachedAddress(member.inboxId, ethIdentifier.identifier);
138
+ cachedCount++;
139
+ }
140
+ }
141
+ }
142
+ catch (error) {
143
+ console.error("Error pre-caching conversation members:", error);
144
+ }
145
+ }
146
+ console.log(`✅ Pre-cached ${cachedCount} address mappings`);
147
+ }
148
+ catch (error) {
149
+ console.error("Error pre-populating cache:", error);
150
+ }
151
+ }
152
+ /**
153
+ * Clear the cache
154
+ */
155
+ clearCache() {
156
+ this.cache.clear();
157
+ console.log("🗑️ Address cache cleared");
158
+ }
159
+ /**
160
+ * Get cache statistics
161
+ */
162
+ getCacheStats() {
163
+ return {
164
+ size: this.cache.size,
165
+ maxSize: this.maxCacheSize
166
+ };
167
+ }
168
+ }