@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,95 @@
1
+ import type { XmtpClient, XmtpMessage } from "../types";
2
+ interface XmtpResolverOptions {
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
+ * Maximum number of messages to cache
15
+ * @default 1000
16
+ */
17
+ maxMessageCacheSize?: number;
18
+ /**
19
+ * Message cache TTL in milliseconds
20
+ * @default 3600000 (1 hour)
21
+ */
22
+ messageCacheTtl?: number;
23
+ }
24
+ export declare class XmtpResolver {
25
+ private client;
26
+ private addressCache;
27
+ private messageCache;
28
+ private readonly maxCacheSize;
29
+ private readonly cacheTtl;
30
+ private readonly maxMessageCacheSize;
31
+ private readonly messageCacheTtl;
32
+ constructor(client: XmtpClient, options?: XmtpResolverOptions);
33
+ /**
34
+ * Resolve user address from inbox ID with caching
35
+ */
36
+ resolveAddress(inboxId: string, conversationId?: string): Promise<`0x${string}` | null>;
37
+ /**
38
+ * Find any message by ID with caching
39
+ */
40
+ findMessage(messageId: string): Promise<XmtpMessage | null>;
41
+ /**
42
+ * Find root message with caching
43
+ */
44
+ findRootMessage(messageId: string): Promise<XmtpMessage | null>;
45
+ /**
46
+ * Recursively finds the root message in a reply chain by following reply references
47
+ */
48
+ private findRootMessageRecursive;
49
+ /**
50
+ * Resolve address from conversation members
51
+ */
52
+ private resolveFromConversation;
53
+ /**
54
+ * Resolve address from inbox state (network fallback)
55
+ */
56
+ private resolveFromInboxState;
57
+ /**
58
+ * Get cached address if not expired
59
+ */
60
+ private getCachedAddress;
61
+ /**
62
+ * Cache address with LRU eviction
63
+ */
64
+ private setCachedAddress;
65
+ /**
66
+ * Get cached message if not expired
67
+ */
68
+ private getCachedMessage;
69
+ /**
70
+ * Cache message with LRU eviction
71
+ */
72
+ private setCachedMessage;
73
+ /**
74
+ * Pre-populate address cache from existing conversations
75
+ */
76
+ prePopulateCache(): Promise<void>;
77
+ /**
78
+ * Clear all caches
79
+ */
80
+ clearCache(): void;
81
+ /**
82
+ * Get cache statistics
83
+ */
84
+ getCacheStats(): {
85
+ address: {
86
+ size: number;
87
+ maxSize: number;
88
+ };
89
+ message: {
90
+ size: number;
91
+ maxSize: number;
92
+ };
93
+ };
94
+ }
95
+ export {};
@@ -0,0 +1,297 @@
1
+ export class XmtpResolver {
2
+ constructor(client, options = {}) {
3
+ this.client = client;
4
+ this.addressCache = new Map();
5
+ this.messageCache = new Map();
6
+ this.maxCacheSize = options.maxCacheSize ?? 1000;
7
+ this.cacheTtl = options.cacheTtl ?? 86400000; // 24 hours
8
+ this.maxMessageCacheSize = options.maxMessageCacheSize ?? 1000;
9
+ this.messageCacheTtl = options.messageCacheTtl ?? 3600000; // 1 hour
10
+ }
11
+ /**
12
+ * Resolve user address from inbox ID with caching
13
+ */
14
+ async resolveAddress(inboxId, conversationId) {
15
+ // Check cache first (fastest)
16
+ const cached = this.getCachedAddress(inboxId);
17
+ if (cached) {
18
+ console.log(`✅ [XmtpResolver] Resolved user address from cache: ${cached}`);
19
+ return cached;
20
+ }
21
+ let userAddress = undefined;
22
+ try {
23
+ // Try conversation members lookup first (faster than network call)
24
+ if (conversationId) {
25
+ const conversation = await this.client.conversations.getConversationById(conversationId);
26
+ if (conversation) {
27
+ userAddress = await this.resolveFromConversation(conversation, inboxId);
28
+ if (userAddress) {
29
+ this.setCachedAddress(inboxId, userAddress);
30
+ console.log(`✅ [XmtpResolver] Resolved user address: ${userAddress}`);
31
+ return userAddress;
32
+ }
33
+ }
34
+ }
35
+ // Fallback to inboxStateFromInboxIds
36
+ userAddress = await this.resolveFromInboxState(inboxId);
37
+ if (userAddress) {
38
+ this.setCachedAddress(inboxId, userAddress);
39
+ console.log(`✅ [XmtpResolver] Resolved user address via fallback: ${userAddress}`);
40
+ return userAddress;
41
+ }
42
+ console.log(`⚠️ [XmtpResolver] No identifiers found for inbox ${inboxId}`);
43
+ return null;
44
+ }
45
+ catch (error) {
46
+ console.error(`❌ [XmtpResolver] Error resolving user address for ${inboxId}:`, error);
47
+ return null;
48
+ }
49
+ }
50
+ /**
51
+ * Find any message by ID with caching
52
+ */
53
+ async findMessage(messageId) {
54
+ // Check cache first
55
+ const cached = this.getCachedMessage(messageId);
56
+ if (cached !== undefined) {
57
+ console.log(cached
58
+ ? `✅ [XmtpResolver] Found message from cache: ${cached.id}`
59
+ : `✅ [XmtpResolver] Found cached null message for: ${messageId}`);
60
+ return cached;
61
+ }
62
+ try {
63
+ console.log(`🔍 [XmtpResolver] Finding message: ${messageId}`);
64
+ const message = await this.client.conversations.getMessageById(messageId);
65
+ if (message) {
66
+ this.setCachedMessage(messageId, message);
67
+ console.log(`✅ [XmtpResolver] Found and cached message: ${message.id}`);
68
+ return message;
69
+ }
70
+ console.log(`⚠️ [XmtpResolver] Message not found: ${messageId}`);
71
+ this.setCachedMessage(messageId, null);
72
+ return null;
73
+ }
74
+ catch (error) {
75
+ console.error(`❌ [XmtpResolver] Error finding message ${messageId}:`, error);
76
+ this.setCachedMessage(messageId, null);
77
+ return null;
78
+ }
79
+ }
80
+ /**
81
+ * Find root message with caching
82
+ */
83
+ async findRootMessage(messageId) {
84
+ // Check if we already have the root cached with a special key
85
+ const rootCacheKey = `root:${messageId}`;
86
+ const cached = this.getCachedMessage(rootCacheKey);
87
+ if (cached !== undefined) {
88
+ console.log(cached
89
+ ? `✅ [XmtpResolver] Found root message from cache: ${cached.id}`
90
+ : `✅ [XmtpResolver] Found cached null root for: ${messageId}`);
91
+ return cached;
92
+ }
93
+ try {
94
+ console.log(`🔍 [XmtpResolver] Finding root message for: ${messageId}`);
95
+ const rootMessage = await this.findRootMessageRecursive(messageId);
96
+ if (rootMessage) {
97
+ this.setCachedMessage(rootCacheKey, rootMessage);
98
+ console.log(`✅ [XmtpResolver] Found and cached root message: ${rootMessage.id}`);
99
+ return rootMessage;
100
+ }
101
+ console.log(`⚠️ [XmtpResolver] No root message found for: ${messageId}`);
102
+ this.setCachedMessage(rootCacheKey, null);
103
+ return null;
104
+ }
105
+ catch (error) {
106
+ console.error(`❌ [XmtpResolver] Error finding root message for ${messageId}:`, error);
107
+ this.setCachedMessage(rootCacheKey, null);
108
+ return null;
109
+ }
110
+ }
111
+ /**
112
+ * Recursively finds the root message in a reply chain by following reply references
113
+ */
114
+ async findRootMessageRecursive(messageId, visitedIds = new Set()) {
115
+ // Prevent infinite loops
116
+ if (visitedIds.has(messageId)) {
117
+ console.warn(`⚠️ Circular reference detected in message chain at ${messageId}`);
118
+ return null;
119
+ }
120
+ visitedIds.add(messageId);
121
+ const message = await this.client.conversations.getMessageById(messageId);
122
+ if (!message) {
123
+ console.warn(`⚠️ [findRootMessage] Message not found: ${messageId}`);
124
+ return null;
125
+ }
126
+ // Debug: Log the raw message structure as returned by XMTP client
127
+ console.log(`🔍 [findRootMessage] Raw message ${messageId}:`, {
128
+ id: message.id,
129
+ contentType: message.contentType,
130
+ content: message.content,
131
+ sentAt: message.sentAt
132
+ });
133
+ // Method 1: Try the parameters (as seen in webhook data)
134
+ if (message.content?.reference) {
135
+ return this.findRootMessageRecursive(message.content.reference, visitedIds);
136
+ }
137
+ return message;
138
+ }
139
+ /**
140
+ * Resolve address from conversation members
141
+ */
142
+ async resolveFromConversation(conversation, inboxId) {
143
+ try {
144
+ const members = await conversation.members();
145
+ const sender = members.find((member) => member.inboxId.toLowerCase() === inboxId.toLowerCase());
146
+ if (sender) {
147
+ const ethIdentifier = sender.accountIdentifiers.find((id) => id.identifierKind === 0 // IdentifierKind.Ethereum
148
+ );
149
+ if (ethIdentifier) {
150
+ return ethIdentifier.identifier;
151
+ }
152
+ else {
153
+ console.log(`⚠️ [XmtpResolver] No Ethereum identifier found for inbox ${inboxId}`);
154
+ }
155
+ }
156
+ else {
157
+ console.log(`⚠️ [XmtpResolver] Sender not found in conversation members for inbox ${inboxId}`);
158
+ }
159
+ }
160
+ catch (error) {
161
+ console.error(`❌ [XmtpResolver] Error resolving from conversation members:`, error);
162
+ }
163
+ return null;
164
+ }
165
+ /**
166
+ * Resolve address from inbox state (network fallback)
167
+ */
168
+ async resolveFromInboxState(inboxId) {
169
+ try {
170
+ const inboxState = await this.client.preferences.inboxStateFromInboxIds([
171
+ inboxId
172
+ ]);
173
+ const firstState = inboxState?.[0];
174
+ if (firstState?.identifiers && firstState.identifiers.length > 0) {
175
+ const firstIdentifier = firstState.identifiers[0];
176
+ return firstIdentifier?.identifier;
177
+ }
178
+ }
179
+ catch (error) {
180
+ console.error(`❌ [XmtpResolver] Error resolving from inbox state:`, error);
181
+ }
182
+ return null;
183
+ }
184
+ /**
185
+ * Get cached address if not expired
186
+ */
187
+ getCachedAddress(inboxId) {
188
+ const entry = this.addressCache.get(inboxId);
189
+ if (!entry)
190
+ return null;
191
+ const now = Date.now();
192
+ if (now - entry.timestamp > this.cacheTtl) {
193
+ this.addressCache.delete(inboxId);
194
+ return null;
195
+ }
196
+ return entry.address;
197
+ }
198
+ /**
199
+ * Cache address with LRU eviction
200
+ */
201
+ setCachedAddress(inboxId, address) {
202
+ // Simple LRU: if cache is full, remove oldest entry
203
+ if (this.addressCache.size >= this.maxCacheSize) {
204
+ const firstKey = this.addressCache.keys().next().value;
205
+ if (firstKey) {
206
+ this.addressCache.delete(firstKey);
207
+ }
208
+ }
209
+ this.addressCache.set(inboxId, {
210
+ address,
211
+ timestamp: Date.now()
212
+ });
213
+ }
214
+ /**
215
+ * Get cached message if not expired
216
+ */
217
+ getCachedMessage(messageId) {
218
+ const entry = this.messageCache.get(messageId);
219
+ if (!entry)
220
+ return undefined;
221
+ const now = Date.now();
222
+ if (now - entry.timestamp > this.messageCacheTtl) {
223
+ this.messageCache.delete(messageId);
224
+ return undefined;
225
+ }
226
+ return entry.message;
227
+ }
228
+ /**
229
+ * Cache message with LRU eviction
230
+ */
231
+ setCachedMessage(messageId, message) {
232
+ // Simple LRU: if cache is full, remove oldest entry
233
+ if (this.messageCache.size >= this.maxMessageCacheSize) {
234
+ const firstKey = this.messageCache.keys().next().value;
235
+ if (firstKey) {
236
+ this.messageCache.delete(firstKey);
237
+ }
238
+ }
239
+ this.messageCache.set(messageId, {
240
+ message,
241
+ timestamp: Date.now()
242
+ });
243
+ }
244
+ /**
245
+ * Pre-populate address cache from existing conversations
246
+ */
247
+ async prePopulateCache() {
248
+ console.log("🔄 [XmtpResolver] Pre-populating address cache...");
249
+ try {
250
+ const conversations = await this.client.conversations.list();
251
+ let cachedCount = 0;
252
+ for (const conversation of conversations) {
253
+ try {
254
+ const members = await conversation.members();
255
+ for (const member of members) {
256
+ const ethIdentifier = member.accountIdentifiers.find((id) => id.identifierKind === 0 // IdentifierKind.Ethereum
257
+ );
258
+ if (ethIdentifier) {
259
+ this.setCachedAddress(member.inboxId, ethIdentifier.identifier);
260
+ cachedCount++;
261
+ }
262
+ }
263
+ }
264
+ catch (error) {
265
+ console.error("[XmtpResolver] Error pre-caching conversation members:", error);
266
+ }
267
+ }
268
+ console.log(`✅ [XmtpResolver] Pre-cached ${cachedCount} address mappings`);
269
+ }
270
+ catch (error) {
271
+ console.error("[XmtpResolver] Error pre-populating cache:", error);
272
+ }
273
+ }
274
+ /**
275
+ * Clear all caches
276
+ */
277
+ clearCache() {
278
+ this.addressCache.clear();
279
+ this.messageCache.clear();
280
+ console.log("🗑️ [XmtpResolver] All caches cleared");
281
+ }
282
+ /**
283
+ * Get cache statistics
284
+ */
285
+ getCacheStats() {
286
+ return {
287
+ address: {
288
+ size: this.addressCache.size,
289
+ maxSize: this.maxCacheSize
290
+ },
291
+ message: {
292
+ size: this.messageCache.size,
293
+ maxSize: this.maxMessageCacheSize
294
+ }
295
+ };
296
+ }
297
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @fileoverview XMTP Service Client Library
3
+ *
4
+ * Clean, reusable client for making HTTP calls to the XMTP listener service.
5
+ * Handles authentication, request formatting, and error handling.
6
+ *
7
+ * This is different from the direct XMTP client - this is for external services
8
+ * talking to our XMTP listener service.
9
+ */
10
+ import type { GetMessageParams, SendMessageParams, SendMessageResponse, SendReactionParams, SendReactionResponse, SendReplyParams, SendReplyResponse, SendTransactionParams, SendTransactionResponse, XmtpServiceClientConfig, XmtpServiceMessage, XmtpServiceResponse } from "./types.js";
11
+ export declare class XmtpServiceClient {
12
+ private config;
13
+ constructor(config: XmtpServiceClientConfig);
14
+ private request;
15
+ sendMessage(params: SendMessageParams): Promise<XmtpServiceResponse<SendMessageResponse>>;
16
+ sendReply(params: SendReplyParams): Promise<XmtpServiceResponse<SendReplyResponse>>;
17
+ sendReaction(params: SendReactionParams): Promise<XmtpServiceResponse<SendReactionResponse>>;
18
+ sendTransaction(params: SendTransactionParams): Promise<XmtpServiceResponse<SendTransactionResponse>>;
19
+ /**
20
+ * Get a single message by ID
21
+ */
22
+ getMessage(params: GetMessageParams): Promise<XmtpServiceResponse<XmtpServiceMessage>>;
23
+ }
24
+ /**
25
+ * Create an XMTP service client from runtime context
26
+ * Expects the runtime context to have xmtpServiceUrl and xmtpServiceToken
27
+ */
28
+ export declare function createXmtpServiceClient(serviceUrl: string, serviceToken: string): XmtpServiceClient;
29
+ export interface XmtpAuthConfig {
30
+ serviceUrl: string;
31
+ serviceToken: string;
32
+ source: "callback" | "environment";
33
+ }
34
+ /**
35
+ * Get XMTP authentication configuration from multiple sources
36
+ * Priority: callback credentials > environment credentials
37
+ */
38
+ export declare function getXmtpAuthConfig(callbackUrl?: string, callbackToken?: string): XmtpAuthConfig | null;
39
+ /**
40
+ * Create an authenticated XMTP service client
41
+ * Handles both callback and environment credential sources
42
+ */
43
+ export declare function createAuthenticatedXmtpClient(callbackUrl?: string, callbackToken?: string): XmtpServiceClient;
44
+ /**
45
+ * Constructs a URL for XMTP tools API endpoints with token authentication
46
+ *
47
+ * @param {string} baseUrl - The base URL of the XMTP service (e.g., "https://api.example.com")
48
+ * @param {string} action - The specific action/endpoint to call (e.g., "send", "receive", "status")
49
+ * @param {string} token - Authentication token (either JWT or API key)
50
+ * @returns {string} Complete URL with token as query parameter
51
+ *
52
+ * @description
53
+ * Builds URLs for XMTP tools endpoints using query parameter authentication.
54
+ * The token is appended as a query parameter for GET request authentication,
55
+ * following the pattern: `/xmtp-tools/{action}?token={token}`
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * const url = getXMTPToolsUrl(
60
+ * "https://api.hybrid.dev",
61
+ * "send",
62
+ * "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
63
+ * );
64
+ * // Returns: "https://api.hybrid.dev/xmtp-tools/send?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
65
+ * ```
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * // Using with API key
70
+ * const url = getXMTPToolsUrl(
71
+ * process.env.XMTP_BASE_URL,
72
+ * "status",
73
+ * process.env.XMTP_API_KEY
74
+ * );
75
+ * ```
76
+ */
77
+ export declare function getXMTPToolsUrl(baseUrl: string, action: string, token: string): string;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @fileoverview XMTP Service Client Library
3
+ *
4
+ * Clean, reusable client for making HTTP calls to the XMTP listener service.
5
+ * Handles authentication, request formatting, and error handling.
6
+ *
7
+ * This is different from the direct XMTP client - this is for external services
8
+ * talking to our XMTP listener service.
9
+ */
10
+ export class XmtpServiceClient {
11
+ constructor(config) {
12
+ this.config = config;
13
+ }
14
+ async request(endpoint, body, method = "POST") {
15
+ try {
16
+ const baseUrl = this.config.serviceUrl.replace(/\/+$/, "");
17
+ // Use Authorization header for xmtp-tools endpoints, query parameter for others
18
+ const isXmtpToolsEndpoint = endpoint.startsWith("/xmtp-tools/");
19
+ const url = `${baseUrl}${endpoint}?token=${this.config.serviceToken}`;
20
+ const headers = {
21
+ "Content-Type": "application/json"
22
+ };
23
+ // Add Authorization header for xmtp-tools endpoints
24
+ if (isXmtpToolsEndpoint) {
25
+ headers.Authorization = `Bearer ${this.config.serviceToken}`;
26
+ }
27
+ const fetchOptions = {
28
+ method,
29
+ headers
30
+ };
31
+ if (method === "POST" && body) {
32
+ fetchOptions.body = JSON.stringify(body);
33
+ }
34
+ const response = await fetch(url, fetchOptions);
35
+ if (!response.ok) {
36
+ let errorMessage = `HTTP ${response.status}`;
37
+ try {
38
+ const responseText = await response.text();
39
+ try {
40
+ const errorData = JSON.parse(responseText);
41
+ errorMessage = errorData.error || errorMessage;
42
+ }
43
+ catch {
44
+ errorMessage = responseText || errorMessage;
45
+ }
46
+ }
47
+ catch {
48
+ // If we can't read the response at all, use the status
49
+ }
50
+ throw new Error(errorMessage);
51
+ }
52
+ return {
53
+ success: true,
54
+ data: (await response.json())
55
+ };
56
+ }
57
+ catch (error) {
58
+ console.error(`❌ [XmtpServiceClient] Request to ${endpoint} failed:`, error);
59
+ return {
60
+ success: false,
61
+ error: error instanceof Error ? error.message : "Unknown error"
62
+ };
63
+ }
64
+ }
65
+ async sendMessage(params) {
66
+ return this.request("/xmtp-tools/send", {
67
+ content: params.content
68
+ });
69
+ }
70
+ async sendReply(params) {
71
+ return this.request("/xmtp-tools/reply", {
72
+ content: params.content,
73
+ messageId: params.messageId
74
+ });
75
+ }
76
+ async sendReaction(params) {
77
+ return this.request("/xmtp-tools/react", {
78
+ messageId: params.messageId,
79
+ emoji: params.emoji,
80
+ action: params.action
81
+ });
82
+ }
83
+ async sendTransaction(params) {
84
+ return this.request("/xmtp-tools/transaction", {
85
+ fromAddress: params.fromAddress,
86
+ chainId: params.chainId,
87
+ calls: params.calls.map((call) => ({
88
+ to: call.to,
89
+ data: call.data,
90
+ ...(call.gas && { gas: call.gas }),
91
+ value: call.value || "0x0",
92
+ metadata: {
93
+ ...call.metadata,
94
+ chainId: params.chainId,
95
+ from: params.fromAddress,
96
+ version: "1"
97
+ }
98
+ }))
99
+ });
100
+ }
101
+ /**
102
+ * Get a single message by ID
103
+ */
104
+ async getMessage(params) {
105
+ return this.request(`/xmtp-tools/messages/${params.messageId}`, undefined, "GET");
106
+ }
107
+ }
108
+ /**
109
+ * Create an XMTP service client from runtime context
110
+ * Expects the runtime context to have xmtpServiceUrl and xmtpServiceToken
111
+ */
112
+ export function createXmtpServiceClient(serviceUrl, serviceToken) {
113
+ if (!serviceUrl || !serviceToken) {
114
+ throw new Error("Missing XMTP service URL or token from runtime context");
115
+ }
116
+ return new XmtpServiceClient({
117
+ serviceUrl,
118
+ serviceToken
119
+ });
120
+ }
121
+ /**
122
+ * Get XMTP authentication configuration from multiple sources
123
+ * Priority: callback credentials > environment credentials
124
+ */
125
+ export function getXmtpAuthConfig(callbackUrl, callbackToken) {
126
+ // Priority 1: Use callback credentials if available
127
+ if (callbackUrl && callbackToken) {
128
+ console.log("🔑 [XmtpAuth] Using callback-provided credentials");
129
+ return {
130
+ serviceUrl: callbackUrl,
131
+ serviceToken: callbackToken,
132
+ source: "callback"
133
+ };
134
+ }
135
+ // Priority 2: Use environment credentials
136
+ const envUrl = process.env.XMTP_HOST;
137
+ const envToken = process.env.XMTP_API_KEY;
138
+ if (envUrl && envToken) {
139
+ console.log("🔑 [XmtpAuth] Using environment credentials");
140
+ return {
141
+ serviceUrl: envUrl,
142
+ serviceToken: envToken,
143
+ source: "environment"
144
+ };
145
+ }
146
+ // No valid credentials found
147
+ console.error("❌ [XmtpAuth] No XMTP credentials found in callback or environment");
148
+ console.error("💡 [XmtpAuth] Expected: XMTP_HOST + XMTP_API_KEY or callback credentials");
149
+ return null;
150
+ }
151
+ /**
152
+ * Create an authenticated XMTP service client
153
+ * Handles both callback and environment credential sources
154
+ */
155
+ export function createAuthenticatedXmtpClient(callbackUrl, callbackToken) {
156
+ const authConfig = getXmtpAuthConfig(callbackUrl, callbackToken);
157
+ if (!authConfig) {
158
+ throw new Error("No XMTP credentials found");
159
+ }
160
+ console.log(`🔗 [XmtpAuth] Creating XMTP client (${authConfig.source} credentials)`);
161
+ return createXmtpServiceClient(authConfig.serviceUrl, authConfig.serviceToken);
162
+ }
163
+ /**
164
+ * Constructs a URL for XMTP tools API endpoints with token authentication
165
+ *
166
+ * @param {string} baseUrl - The base URL of the XMTP service (e.g., "https://api.example.com")
167
+ * @param {string} action - The specific action/endpoint to call (e.g., "send", "receive", "status")
168
+ * @param {string} token - Authentication token (either JWT or API key)
169
+ * @returns {string} Complete URL with token as query parameter
170
+ *
171
+ * @description
172
+ * Builds URLs for XMTP tools endpoints using query parameter authentication.
173
+ * The token is appended as a query parameter for GET request authentication,
174
+ * following the pattern: `/xmtp-tools/{action}?token={token}`
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * const url = getXMTPToolsUrl(
179
+ * "https://api.hybrid.dev",
180
+ * "send",
181
+ * "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
182
+ * );
183
+ * // Returns: "https://api.hybrid.dev/xmtp-tools/send?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
184
+ * ```
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * // Using with API key
189
+ * const url = getXMTPToolsUrl(
190
+ * process.env.XMTP_BASE_URL,
191
+ * "status",
192
+ * process.env.XMTP_API_KEY
193
+ * );
194
+ * ```
195
+ */
196
+ export function getXMTPToolsUrl(baseUrl, action, token) {
197
+ return `${baseUrl}/xmtp-tools/${action}?token=${token}`;
198
+ }