@azeth/sdk 0.2.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/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/account/balance.d.ts +41 -0
- package/dist/account/balance.d.ts.map +1 -0
- package/dist/account/balance.js +264 -0
- package/dist/account/balance.js.map +1 -0
- package/dist/account/create.d.ts +27 -0
- package/dist/account/create.d.ts.map +1 -0
- package/dist/account/create.js +116 -0
- package/dist/account/create.js.map +1 -0
- package/dist/account/deposit.d.ts +34 -0
- package/dist/account/deposit.d.ts.map +1 -0
- package/dist/account/deposit.js +88 -0
- package/dist/account/deposit.js.map +1 -0
- package/dist/account/guardian-approval.d.ts +111 -0
- package/dist/account/guardian-approval.d.ts.map +1 -0
- package/dist/account/guardian-approval.js +223 -0
- package/dist/account/guardian-approval.js.map +1 -0
- package/dist/account/guardian.d.ts +27 -0
- package/dist/account/guardian.d.ts.map +1 -0
- package/dist/account/guardian.js +67 -0
- package/dist/account/guardian.js.map +1 -0
- package/dist/account/history.d.ts +22 -0
- package/dist/account/history.d.ts.map +1 -0
- package/dist/account/history.js +144 -0
- package/dist/account/history.js.map +1 -0
- package/dist/account/transfer.d.ts +28 -0
- package/dist/account/transfer.d.ts.map +1 -0
- package/dist/account/transfer.js +137 -0
- package/dist/account/transfer.js.map +1 -0
- package/dist/auth/erc8128.d.ts +14 -0
- package/dist/auth/erc8128.d.ts.map +1 -0
- package/dist/auth/erc8128.js +92 -0
- package/dist/auth/erc8128.js.map +1 -0
- package/dist/client.d.ts +394 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +970 -0
- package/dist/client.js.map +1 -0
- package/dist/events/emitter.d.ts +96 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +90 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/messaging/message-router.d.ts +69 -0
- package/dist/messaging/message-router.d.ts.map +1 -0
- package/dist/messaging/message-router.js +307 -0
- package/dist/messaging/message-router.js.map +1 -0
- package/dist/messaging/rate-limiter.d.ts +31 -0
- package/dist/messaging/rate-limiter.d.ts.map +1 -0
- package/dist/messaging/rate-limiter.js +74 -0
- package/dist/messaging/rate-limiter.js.map +1 -0
- package/dist/messaging/xmtp.d.ts +144 -0
- package/dist/messaging/xmtp.d.ts.map +1 -0
- package/dist/messaging/xmtp.js +473 -0
- package/dist/messaging/xmtp.js.map +1 -0
- package/dist/payments/agreements.d.ts +87 -0
- package/dist/payments/agreements.d.ts.map +1 -0
- package/dist/payments/agreements.js +337 -0
- package/dist/payments/agreements.js.map +1 -0
- package/dist/payments/budget.d.ts +118 -0
- package/dist/payments/budget.d.ts.map +1 -0
- package/dist/payments/budget.js +176 -0
- package/dist/payments/budget.js.map +1 -0
- package/dist/payments/smart-fetch.d.ts +65 -0
- package/dist/payments/smart-fetch.d.ts.map +1 -0
- package/dist/payments/smart-fetch.js +115 -0
- package/dist/payments/smart-fetch.js.map +1 -0
- package/dist/payments/x402.d.ts +89 -0
- package/dist/payments/x402.d.ts.map +1 -0
- package/dist/payments/x402.js +620 -0
- package/dist/payments/x402.js.map +1 -0
- package/dist/registry/discover.d.ts +43 -0
- package/dist/registry/discover.d.ts.map +1 -0
- package/dist/registry/discover.js +272 -0
- package/dist/registry/discover.js.map +1 -0
- package/dist/registry/register.d.ts +44 -0
- package/dist/registry/register.d.ts.map +1 -0
- package/dist/registry/register.js +126 -0
- package/dist/registry/register.js.map +1 -0
- package/dist/reputation/opinion.d.ts +52 -0
- package/dist/reputation/opinion.d.ts.map +1 -0
- package/dist/reputation/opinion.js +198 -0
- package/dist/reputation/opinion.js.map +1 -0
- package/dist/utils/addresses.d.ts +6 -0
- package/dist/utils/addresses.d.ts.map +1 -0
- package/dist/utils/addresses.js +53 -0
- package/dist/utils/addresses.js.map +1 -0
- package/dist/utils/errors.d.ts +23 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +188 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/execution.d.ts +20 -0
- package/dist/utils/execution.d.ts.map +1 -0
- package/dist/utils/execution.js +28 -0
- package/dist/utils/execution.js.map +1 -0
- package/dist/utils/paymaster.d.ts +35 -0
- package/dist/utils/paymaster.d.ts.map +1 -0
- package/dist/utils/paymaster.js +115 -0
- package/dist/utils/paymaster.js.map +1 -0
- package/dist/utils/retry.d.ts +19 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +68 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/userop.d.ts +55 -0
- package/dist/utils/userop.d.ts.map +1 -0
- package/dist/utils/userop.js +201 -0
- package/dist/utils/userop.js.map +1 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +35 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { RateLimiter } from './rate-limiter.js';
|
|
2
|
+
// ── Named constants ──
|
|
3
|
+
/** Default maximum messages per sender per 60-second window */
|
|
4
|
+
const DEFAULT_MAX_MESSAGES_PER_MINUTE = 10;
|
|
5
|
+
/** Default minimum reputation score required to invoke services */
|
|
6
|
+
const DEFAULT_MIN_REPUTATION = 30;
|
|
7
|
+
/** Maximum tracked senders before the rate limiter evicts old entries */
|
|
8
|
+
const MAX_TRACKED_SENDERS = 10_000;
|
|
9
|
+
/** Message router for XMTP structured message dispatch.
|
|
10
|
+
*
|
|
11
|
+
* Receives raw message strings, parses them, dispatches by type,
|
|
12
|
+
* and returns JSON response strings. Handles rate limiting, reputation
|
|
13
|
+
* gating, service execution, and capabilities advertising.
|
|
14
|
+
*/
|
|
15
|
+
export class MessageRouter {
|
|
16
|
+
_skills;
|
|
17
|
+
_agentName;
|
|
18
|
+
_agentAddress;
|
|
19
|
+
_httpEndpoint;
|
|
20
|
+
_minReputationForService;
|
|
21
|
+
_reputationChecker;
|
|
22
|
+
_onFriendRequest;
|
|
23
|
+
_onFriendAccept;
|
|
24
|
+
_textFallbackHandler;
|
|
25
|
+
_handlers = new Map();
|
|
26
|
+
_rateLimiter;
|
|
27
|
+
/** Create a new MessageRouter with the given options.
|
|
28
|
+
*
|
|
29
|
+
* @param options - Router configuration including skills, agent identity, and callbacks
|
|
30
|
+
*/
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this._skills = options.skills;
|
|
33
|
+
this._agentName = options.agentName;
|
|
34
|
+
this._agentAddress = options.agentAddress;
|
|
35
|
+
this._httpEndpoint = options.httpEndpoint;
|
|
36
|
+
this._minReputationForService = options.minReputationForService ?? DEFAULT_MIN_REPUTATION;
|
|
37
|
+
this._reputationChecker = options.reputationChecker;
|
|
38
|
+
this._onFriendRequest = options.onFriendRequest;
|
|
39
|
+
this._onFriendAccept = options.onFriendAccept;
|
|
40
|
+
this._textFallbackHandler = options.textFallbackHandler;
|
|
41
|
+
this._rateLimiter = new RateLimiter(options.maxMessagesPerMinute ?? DEFAULT_MAX_MESSAGES_PER_MINUTE, MAX_TRACKED_SENDERS);
|
|
42
|
+
}
|
|
43
|
+
/** Route an incoming message to the appropriate handler and return a JSON response.
|
|
44
|
+
*
|
|
45
|
+
* Flow: rate limit check → JSON parse → structured dispatch or text fallback.
|
|
46
|
+
* All responses are valid JSON strings.
|
|
47
|
+
*
|
|
48
|
+
* @param sender - Sender identifier (address or inbox ID)
|
|
49
|
+
* @param content - Raw message content string
|
|
50
|
+
* @returns JSON response string
|
|
51
|
+
*/
|
|
52
|
+
async routeMessage(sender, content) {
|
|
53
|
+
// Rate limit check
|
|
54
|
+
if (!this._rateLimiter.checkLimit(sender)) {
|
|
55
|
+
const resp = {
|
|
56
|
+
type: 'error',
|
|
57
|
+
error: 'Rate limit exceeded',
|
|
58
|
+
code: 'RATE_LIMITED',
|
|
59
|
+
};
|
|
60
|
+
return JSON.stringify(resp);
|
|
61
|
+
}
|
|
62
|
+
// Try JSON parse
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(content);
|
|
65
|
+
if (typeof parsed === 'object' &&
|
|
66
|
+
parsed !== null &&
|
|
67
|
+
'type' in parsed &&
|
|
68
|
+
typeof parsed.type === 'string') {
|
|
69
|
+
return await this._handleStructuredMessage(sender, parsed);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Not valid JSON — fall through to text handler
|
|
74
|
+
}
|
|
75
|
+
// Text fallback
|
|
76
|
+
return await this._handleTextMessage(sender, content);
|
|
77
|
+
}
|
|
78
|
+
/** Register a handler function for a named service.
|
|
79
|
+
*
|
|
80
|
+
* The handler will be invoked when a `service-request` message
|
|
81
|
+
* arrives for a free skill matching the given name.
|
|
82
|
+
*
|
|
83
|
+
* @param serviceName - Service name (case-insensitive matching at dispatch time)
|
|
84
|
+
* @param handler - Async function that processes the request payload
|
|
85
|
+
*/
|
|
86
|
+
registerHandler(serviceName, handler) {
|
|
87
|
+
this._handlers.set(serviceName.toLowerCase(), handler);
|
|
88
|
+
}
|
|
89
|
+
/** Remove a previously registered handler for a service.
|
|
90
|
+
*
|
|
91
|
+
* @param serviceName - Service name to unregister
|
|
92
|
+
*/
|
|
93
|
+
removeHandler(serviceName) {
|
|
94
|
+
this._handlers.delete(serviceName.toLowerCase());
|
|
95
|
+
}
|
|
96
|
+
/** Get the names of all services that have registered handlers.
|
|
97
|
+
*
|
|
98
|
+
* @returns Array of service names with active handlers
|
|
99
|
+
*/
|
|
100
|
+
getRegisteredHandlers() {
|
|
101
|
+
return Array.from(this._handlers.keys());
|
|
102
|
+
}
|
|
103
|
+
/** Build the capabilities response advertising this agent's services.
|
|
104
|
+
*
|
|
105
|
+
* @returns CapabilitiesResponse with service catalog, free/paid separation, and usage examples
|
|
106
|
+
*/
|
|
107
|
+
buildCapabilities() {
|
|
108
|
+
const freeSkills = this._skills.filter(s => !s.price);
|
|
109
|
+
const paidSkills = this._skills.filter(s => !!s.price);
|
|
110
|
+
const firstFree = freeSkills[0];
|
|
111
|
+
return {
|
|
112
|
+
type: 'capabilities',
|
|
113
|
+
agentAddress: this._agentAddress,
|
|
114
|
+
name: this._agentName,
|
|
115
|
+
services: this._skills.map(s => ({
|
|
116
|
+
name: s.name,
|
|
117
|
+
description: s.description,
|
|
118
|
+
price: s.price ?? null,
|
|
119
|
+
method: s.method ?? 'POST',
|
|
120
|
+
})),
|
|
121
|
+
freeServices: freeSkills.map(s => s.name),
|
|
122
|
+
paidServices: paidSkills.map(s => s.name),
|
|
123
|
+
httpEndpoint: this._httpEndpoint,
|
|
124
|
+
usage: {
|
|
125
|
+
free: firstFree
|
|
126
|
+
? { type: 'service-request', service: firstFree.name, payload: {} }
|
|
127
|
+
: null,
|
|
128
|
+
paid: paidSkills.length > 0
|
|
129
|
+
? `Use HTTP with x402 payment at ${this._httpEndpoint ?? '<http-endpoint>'}`
|
|
130
|
+
: null,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Stop the rate limiter cleanup timer.
|
|
135
|
+
*
|
|
136
|
+
* Call this when the router is no longer needed to prevent timer leaks.
|
|
137
|
+
*/
|
|
138
|
+
destroy() {
|
|
139
|
+
this._rateLimiter.destroy();
|
|
140
|
+
}
|
|
141
|
+
// ── Private: Structured message dispatch ──
|
|
142
|
+
async _handleStructuredMessage(sender, message) {
|
|
143
|
+
// Reputation gating for service requests
|
|
144
|
+
if (message.type === 'service-request' && this._reputationChecker) {
|
|
145
|
+
try {
|
|
146
|
+
const rep = await this._reputationChecker(sender);
|
|
147
|
+
if (rep < this._minReputationForService) {
|
|
148
|
+
const resp = {
|
|
149
|
+
type: 'error',
|
|
150
|
+
error: 'Insufficient reputation to use services',
|
|
151
|
+
code: 'INSUFFICIENT_REPUTATION',
|
|
152
|
+
};
|
|
153
|
+
return JSON.stringify(resp);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
const resp = {
|
|
158
|
+
type: 'error',
|
|
159
|
+
error: `Reputation check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
160
|
+
code: 'REPUTATION_CHECK_FAILED',
|
|
161
|
+
};
|
|
162
|
+
return JSON.stringify(resp);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
switch (message.type) {
|
|
166
|
+
case 'service-request':
|
|
167
|
+
return this._handleServiceRequest(sender, message);
|
|
168
|
+
case 'service-inquiry':
|
|
169
|
+
return this._handleServiceInquiry(message);
|
|
170
|
+
case 'friend-request': {
|
|
171
|
+
if (this._onFriendRequest) {
|
|
172
|
+
await this._onFriendRequest(sender, message);
|
|
173
|
+
const resp = { type: 'ack', received: 'friend-request' };
|
|
174
|
+
return JSON.stringify(resp);
|
|
175
|
+
}
|
|
176
|
+
const resp = { type: 'ack', received: 'friend-request', note: 'no handler registered' };
|
|
177
|
+
return JSON.stringify(resp);
|
|
178
|
+
}
|
|
179
|
+
case 'friend-accept': {
|
|
180
|
+
if (this._onFriendAccept) {
|
|
181
|
+
await this._onFriendAccept(sender, message);
|
|
182
|
+
const resp = { type: 'ack', received: 'friend-accept' };
|
|
183
|
+
return JSON.stringify(resp);
|
|
184
|
+
}
|
|
185
|
+
const resp = { type: 'ack', received: 'friend-accept', note: 'no handler registered' };
|
|
186
|
+
return JSON.stringify(resp);
|
|
187
|
+
}
|
|
188
|
+
case 'capabilities':
|
|
189
|
+
return JSON.stringify(this.buildCapabilities());
|
|
190
|
+
default: {
|
|
191
|
+
const resp = { type: 'ack', received: message.type };
|
|
192
|
+
return JSON.stringify(resp);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async _handleServiceRequest(sender, request) {
|
|
197
|
+
// Find matching skill (case-insensitive)
|
|
198
|
+
const skill = this._skills.find(s => s.name.toLowerCase() === request.service.toLowerCase());
|
|
199
|
+
// Unknown skill
|
|
200
|
+
if (!skill) {
|
|
201
|
+
const caps = this.buildCapabilities();
|
|
202
|
+
const resp = {
|
|
203
|
+
type: 'service-response',
|
|
204
|
+
requestId: request.id,
|
|
205
|
+
status: 'error',
|
|
206
|
+
result: `Unknown service: ${request.service}`,
|
|
207
|
+
available: caps.services.map(s => s.name),
|
|
208
|
+
};
|
|
209
|
+
return JSON.stringify(resp);
|
|
210
|
+
}
|
|
211
|
+
// Paid skill — redirect to HTTP + x402
|
|
212
|
+
if (skill.price) {
|
|
213
|
+
const resp = {
|
|
214
|
+
type: 'service-response',
|
|
215
|
+
requestId: request.id,
|
|
216
|
+
status: 'payment-required',
|
|
217
|
+
result: `${request.service} costs ${skill.price}. Use HTTP with x402 payment.`,
|
|
218
|
+
httpEndpoint: this._httpEndpoint
|
|
219
|
+
? `${this._httpEndpoint}/api/${skill.name}`
|
|
220
|
+
: undefined,
|
|
221
|
+
usage: `Pay via x402 at ${this._httpEndpoint ?? '<http-endpoint>'}/api/${skill.name}`,
|
|
222
|
+
};
|
|
223
|
+
return JSON.stringify(resp);
|
|
224
|
+
}
|
|
225
|
+
// Free skill — look up handler
|
|
226
|
+
const handler = this._handlers.get(skill.name.toLowerCase());
|
|
227
|
+
if (!handler) {
|
|
228
|
+
const caps = this.buildCapabilities();
|
|
229
|
+
const resp = {
|
|
230
|
+
type: 'service-response',
|
|
231
|
+
requestId: request.id,
|
|
232
|
+
status: 'error',
|
|
233
|
+
result: `No handler registered for service: ${skill.name}`,
|
|
234
|
+
available: caps.services.map(s => s.name),
|
|
235
|
+
};
|
|
236
|
+
return JSON.stringify(resp);
|
|
237
|
+
}
|
|
238
|
+
// Execute handler
|
|
239
|
+
try {
|
|
240
|
+
const result = await handler(sender, request.payload);
|
|
241
|
+
const resp = {
|
|
242
|
+
type: 'service-response',
|
|
243
|
+
requestId: request.id,
|
|
244
|
+
status: 'success',
|
|
245
|
+
result,
|
|
246
|
+
};
|
|
247
|
+
return JSON.stringify(resp);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const resp = {
|
|
251
|
+
type: 'service-response',
|
|
252
|
+
requestId: request.id,
|
|
253
|
+
status: 'error',
|
|
254
|
+
result: err instanceof Error ? err.message : String(err),
|
|
255
|
+
};
|
|
256
|
+
return JSON.stringify(resp);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
_handleServiceInquiry(inquiry) {
|
|
260
|
+
const skill = this._skills.find(s => s.name.toLowerCase() === inquiry.service.toLowerCase());
|
|
261
|
+
if (skill) {
|
|
262
|
+
const resp = {
|
|
263
|
+
type: 'service-details',
|
|
264
|
+
service: skill.name,
|
|
265
|
+
available: true,
|
|
266
|
+
description: skill.description,
|
|
267
|
+
price: skill.price ?? null,
|
|
268
|
+
method: skill.method ?? 'POST',
|
|
269
|
+
paid: !!skill.price,
|
|
270
|
+
capabilities: skill.tags ?? [],
|
|
271
|
+
httpEndpoint: this._httpEndpoint
|
|
272
|
+
? `${this._httpEndpoint}/api/${skill.name}`
|
|
273
|
+
: undefined,
|
|
274
|
+
authentication: 'ERC-8128',
|
|
275
|
+
payment: 'x402',
|
|
276
|
+
};
|
|
277
|
+
return JSON.stringify(resp);
|
|
278
|
+
}
|
|
279
|
+
const resp = {
|
|
280
|
+
type: 'service-details',
|
|
281
|
+
service: inquiry.service,
|
|
282
|
+
available: false,
|
|
283
|
+
allServices: this._skills.map(s => s.name),
|
|
284
|
+
};
|
|
285
|
+
return JSON.stringify(resp);
|
|
286
|
+
}
|
|
287
|
+
// ── Private: Text message fallback ──
|
|
288
|
+
async _handleTextMessage(sender, content) {
|
|
289
|
+
// Try custom text fallback handler
|
|
290
|
+
if (this._textFallbackHandler) {
|
|
291
|
+
const result = await this._textFallbackHandler(sender, content);
|
|
292
|
+
if (result !== null) {
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Default: return capabilities if skills exist
|
|
297
|
+
if (this._skills.length > 0) {
|
|
298
|
+
return JSON.stringify(this.buildCapabilities());
|
|
299
|
+
}
|
|
300
|
+
// No skills configured
|
|
301
|
+
return JSON.stringify({
|
|
302
|
+
type: 'info',
|
|
303
|
+
message: `This agent (${this._agentName}) has no services configured.`,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=message-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-router.js","sourceRoot":"","sources":["../../src/messaging/message-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Per-sender rate limiter for incoming XMTP messages.
|
|
2
|
+
*
|
|
3
|
+
* Tracks timestamps of recent messages per sender and rejects messages
|
|
4
|
+
* that exceed the configured rate. Stale entries are cleaned up on a
|
|
5
|
+
* configurable interval.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RateLimiter {
|
|
8
|
+
private readonly _limits;
|
|
9
|
+
private readonly _maxPerMinute;
|
|
10
|
+
/** M-6: Maximum number of tracked senders to prevent unbounded memory growth */
|
|
11
|
+
private readonly _maxSenders;
|
|
12
|
+
private _cleanupInterval;
|
|
13
|
+
/** @param maxPerMinute Maximum messages per sender per minute (default 10)
|
|
14
|
+
* @param maxSenders Maximum tracked sender entries before eviction (default 10_000) */
|
|
15
|
+
constructor(maxPerMinute?: number, maxSenders?: number);
|
|
16
|
+
/** Check whether a sender is within their rate limit.
|
|
17
|
+
*
|
|
18
|
+
* Records the current timestamp if allowed.
|
|
19
|
+
*
|
|
20
|
+
* @param sender - Sender identifier (typically an address)
|
|
21
|
+
* @returns `true` if the message is within the limit, `false` if rate-limited
|
|
22
|
+
*/
|
|
23
|
+
checkLimit(sender: string): boolean;
|
|
24
|
+
/** Get remaining message quota for a sender within the current minute window */
|
|
25
|
+
getRemainingQuota(sender: string): number;
|
|
26
|
+
/** Purge expired entries from all senders */
|
|
27
|
+
private _cleanup;
|
|
28
|
+
/** Stop the cleanup timer and clear all state */
|
|
29
|
+
destroy(): void;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/messaging/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,gBAAgB,CAA+C;IAEvE;4FACwF;gBAC5E,YAAY,SAAK,EAAE,UAAU,SAAS;IAMlD;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAqBnC,gFAAgF;IAChF,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAOzC,6CAA6C;IAC7C,OAAO,CAAC,QAAQ;IAYhB,iDAAiD;IACjD,OAAO,IAAI,IAAI;CAOhB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Per-sender rate limiter for incoming XMTP messages.
|
|
2
|
+
*
|
|
3
|
+
* Tracks timestamps of recent messages per sender and rejects messages
|
|
4
|
+
* that exceed the configured rate. Stale entries are cleaned up on a
|
|
5
|
+
* configurable interval.
|
|
6
|
+
*/
|
|
7
|
+
export class RateLimiter {
|
|
8
|
+
_limits = new Map();
|
|
9
|
+
_maxPerMinute;
|
|
10
|
+
/** M-6: Maximum number of tracked senders to prevent unbounded memory growth */
|
|
11
|
+
_maxSenders;
|
|
12
|
+
_cleanupInterval = null;
|
|
13
|
+
/** @param maxPerMinute Maximum messages per sender per minute (default 10)
|
|
14
|
+
* @param maxSenders Maximum tracked sender entries before eviction (default 10_000) */
|
|
15
|
+
constructor(maxPerMinute = 10, maxSenders = 10_000) {
|
|
16
|
+
this._maxPerMinute = maxPerMinute;
|
|
17
|
+
this._maxSenders = maxSenders;
|
|
18
|
+
this._cleanupInterval = setInterval(() => this._cleanup(), 300_000);
|
|
19
|
+
}
|
|
20
|
+
/** Check whether a sender is within their rate limit.
|
|
21
|
+
*
|
|
22
|
+
* Records the current timestamp if allowed.
|
|
23
|
+
*
|
|
24
|
+
* @param sender - Sender identifier (typically an address)
|
|
25
|
+
* @returns `true` if the message is within the limit, `false` if rate-limited
|
|
26
|
+
*/
|
|
27
|
+
checkLimit(sender) {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const timestamps = this._limits.get(sender) ?? [];
|
|
30
|
+
const recent = timestamps.filter(ts => now - ts < 60_000);
|
|
31
|
+
if (recent.length >= this._maxPerMinute) {
|
|
32
|
+
this._limits.set(sender, recent);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
// M-6: Evict oldest sender entry if at capacity (FIFO eviction)
|
|
36
|
+
if (this._limits.size >= this._maxSenders && !this._limits.has(sender)) {
|
|
37
|
+
const oldestKey = this._limits.keys().next().value;
|
|
38
|
+
if (oldestKey !== undefined)
|
|
39
|
+
this._limits.delete(oldestKey);
|
|
40
|
+
}
|
|
41
|
+
recent.push(now);
|
|
42
|
+
this._limits.set(sender, recent);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
/** Get remaining message quota for a sender within the current minute window */
|
|
46
|
+
getRemainingQuota(sender) {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const timestamps = this._limits.get(sender) ?? [];
|
|
49
|
+
const recent = timestamps.filter(ts => now - ts < 60_000);
|
|
50
|
+
return Math.max(0, this._maxPerMinute - recent.length);
|
|
51
|
+
}
|
|
52
|
+
/** Purge expired entries from all senders */
|
|
53
|
+
_cleanup() {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
for (const [sender, timestamps] of this._limits.entries()) {
|
|
56
|
+
const recent = timestamps.filter(ts => now - ts < 60_000);
|
|
57
|
+
if (recent.length === 0) {
|
|
58
|
+
this._limits.delete(sender);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this._limits.set(sender, recent);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Stop the cleanup timer and clear all state */
|
|
66
|
+
destroy() {
|
|
67
|
+
if (this._cleanupInterval) {
|
|
68
|
+
clearInterval(this._cleanupInterval);
|
|
69
|
+
this._cleanupInterval = null;
|
|
70
|
+
}
|
|
71
|
+
this._limits.clear();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/messaging/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { type XMTPMessage, type XMTPConfig, type XMTPConversation, type MessageHandler } from '@azeth/common';
|
|
2
|
+
import type { MessageRouter } from './message-router.js';
|
|
3
|
+
export type { XMTPConfig, XMTPConversation };
|
|
4
|
+
/** Parameters for sending a message via XMTP */
|
|
5
|
+
export interface SendMessageParams {
|
|
6
|
+
/** Recipient Ethereum address */
|
|
7
|
+
to: `0x${string}`;
|
|
8
|
+
/** Message content (text) */
|
|
9
|
+
content: string;
|
|
10
|
+
/** Content type hint (default: 'text/plain') */
|
|
11
|
+
contentType?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Interface for the XMTP messaging client */
|
|
14
|
+
export interface MessagingClient {
|
|
15
|
+
/** Initialize the client with a private key and optional config */
|
|
16
|
+
initialize(privateKey: `0x${string}`, config?: XMTPConfig): Promise<void>;
|
|
17
|
+
/** Send a message to an Ethereum address. Returns the conversation ID. */
|
|
18
|
+
sendMessage(params: SendMessageParams): Promise<string>;
|
|
19
|
+
/** Register a handler for incoming messages. Returns an unsubscribe function. */
|
|
20
|
+
onMessage(handler: MessageHandler): () => void;
|
|
21
|
+
/** Check if an address is reachable on the XMTP network */
|
|
22
|
+
canReach(address: `0x${string}`): Promise<boolean>;
|
|
23
|
+
/** List active conversations */
|
|
24
|
+
getConversations(): Promise<XMTPConversation[]>;
|
|
25
|
+
/** Whether the client has been successfully initialized */
|
|
26
|
+
isReady(): boolean;
|
|
27
|
+
/** Tear down the client, stopping the agent and clearing state */
|
|
28
|
+
destroy(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/** XMTP messaging client backed by @xmtp/agent-sdk.
|
|
31
|
+
*
|
|
32
|
+
* Provides E2E encrypted agent-to-agent messaging via the XMTP network.
|
|
33
|
+
* Messages are rate-limited per sender and reachability results are cached.
|
|
34
|
+
*/
|
|
35
|
+
export declare class XMTPClient implements MessagingClient {
|
|
36
|
+
private _agent;
|
|
37
|
+
/** Static Client class from @xmtp/agent-sdk, used for fetchInboxStates.
|
|
38
|
+
* The Agent's client instance doesn't expose this method, so we use the static version. */
|
|
39
|
+
private _ClientClass;
|
|
40
|
+
private _xmtpEnv;
|
|
41
|
+
private _handlers;
|
|
42
|
+
private _rateLimiter;
|
|
43
|
+
private _reachabilityCache;
|
|
44
|
+
private _reachabilityCacheTtlMs;
|
|
45
|
+
private _maxMessageLength;
|
|
46
|
+
private _ready;
|
|
47
|
+
private _listening;
|
|
48
|
+
private _messageStreamAbort;
|
|
49
|
+
private _convStreamAbort;
|
|
50
|
+
private _messageRouter;
|
|
51
|
+
private _autoReply;
|
|
52
|
+
/** Initialize the XMTP client with a private key.
|
|
53
|
+
*
|
|
54
|
+
* Creates an XMTP Agent using the provided private key and config.
|
|
55
|
+
* Must be called before any messaging operations.
|
|
56
|
+
*
|
|
57
|
+
* @param privateKey - Owner's private key (0x-prefixed hex)
|
|
58
|
+
* @param config - Optional XMTP configuration
|
|
59
|
+
* @throws AzethError if initialization fails
|
|
60
|
+
*/
|
|
61
|
+
initialize(privateKey: `0x${string}`, config?: XMTPConfig): Promise<void>;
|
|
62
|
+
/** Send a text message to an Ethereum address.
|
|
63
|
+
*
|
|
64
|
+
* Checks reachability, creates or finds a DM conversation, then sends.
|
|
65
|
+
*
|
|
66
|
+
* @param params - Message parameters (to, content, contentType)
|
|
67
|
+
* @returns The conversation ID
|
|
68
|
+
* @throws AzethError if the recipient is unreachable, content exceeds limits, or sending fails
|
|
69
|
+
*/
|
|
70
|
+
sendMessage(params: SendMessageParams): Promise<string>;
|
|
71
|
+
/** Register a handler for incoming messages.
|
|
72
|
+
*
|
|
73
|
+
* Starts the agent's message listener on first handler registration.
|
|
74
|
+
* Returns an unsubscribe function that removes the handler.
|
|
75
|
+
*
|
|
76
|
+
* @param handler - Async function called for each incoming message
|
|
77
|
+
* @returns Unsubscribe function
|
|
78
|
+
*/
|
|
79
|
+
onMessage(handler: MessageHandler): () => void;
|
|
80
|
+
/** Check if an Ethereum address is reachable on the XMTP network.
|
|
81
|
+
*
|
|
82
|
+
* Results are cached with a configurable TTL (default 5 minutes).
|
|
83
|
+
* Returns `false` on errors rather than throwing.
|
|
84
|
+
*
|
|
85
|
+
* @param address - Ethereum address to check
|
|
86
|
+
* @returns Whether the address can receive XMTP messages
|
|
87
|
+
*/
|
|
88
|
+
canReach(address: `0x${string}`): Promise<boolean>;
|
|
89
|
+
/** List active XMTP conversations.
|
|
90
|
+
*
|
|
91
|
+
* @returns Array of conversation summaries
|
|
92
|
+
*/
|
|
93
|
+
getConversations(): Promise<XMTPConversation[]>;
|
|
94
|
+
/** Read recent messages from a specific conversation.
|
|
95
|
+
*
|
|
96
|
+
* Syncs conversations first, then fetches messages from the conversation
|
|
97
|
+
* matching the given ID. Returns messages sorted by timestamp (newest first).
|
|
98
|
+
*
|
|
99
|
+
* @param conversationId - The XMTP conversation ID to read from
|
|
100
|
+
* @param limit - Maximum number of messages to return (default 20, max 100)
|
|
101
|
+
* @returns Array of messages with sender, content, and timestamp
|
|
102
|
+
*/
|
|
103
|
+
getMessages(conversationId: string, limit?: number): Promise<XMTPMessage[]>;
|
|
104
|
+
/** Read recent messages from a conversation with a specific peer address.
|
|
105
|
+
*
|
|
106
|
+
* Convenience method that finds the conversation by peer address
|
|
107
|
+
* and reads messages from it.
|
|
108
|
+
*
|
|
109
|
+
* @param peerAddress - The Ethereum address of the conversation peer
|
|
110
|
+
* @param limit - Maximum number of messages to return (default 20, max 100)
|
|
111
|
+
* @returns Array of messages, or empty array if no conversation found
|
|
112
|
+
*/
|
|
113
|
+
getMessagesByPeer(peerAddress: `0x${string}`, limit?: number): Promise<XMTPMessage[]>;
|
|
114
|
+
/** Whether the client has been successfully initialized and is ready */
|
|
115
|
+
isReady(): boolean;
|
|
116
|
+
/** Attach a MessageRouter for structured message dispatch.
|
|
117
|
+
*
|
|
118
|
+
* When a router is set and autoReply is enabled (via XMTPConfig or by
|
|
119
|
+
* setting autoReply=true here), incoming messages are automatically
|
|
120
|
+
* routed through the router and responses sent back to the sender.
|
|
121
|
+
*
|
|
122
|
+
* @param router - The MessageRouter instance to use for dispatch
|
|
123
|
+
* @param autoReply - Enable auto-reply (default: uses config value)
|
|
124
|
+
*/
|
|
125
|
+
setRouter(router: MessageRouter, autoReply?: boolean): void;
|
|
126
|
+
/** Tear down the XMTP client.
|
|
127
|
+
*
|
|
128
|
+
* Stops the agent, clears all handlers, caches, and timers.
|
|
129
|
+
*/
|
|
130
|
+
destroy(): Promise<void>;
|
|
131
|
+
private _requireReady;
|
|
132
|
+
/** Start streaming incoming messages and new conversations */
|
|
133
|
+
private _startListening;
|
|
134
|
+
/** Process a single incoming message from the stream */
|
|
135
|
+
private _handleIncoming;
|
|
136
|
+
/** Extract peer address from a conversation object.
|
|
137
|
+
*
|
|
138
|
+
* Uses `peerInboxId` + `client.fetchInboxStates()` to resolve the peer's
|
|
139
|
+
* wallet address. This avoids `conv.members()` which throws an internal
|
|
140
|
+
* error in XMTP SDK v5 due to private field access issues on listed conversations.
|
|
141
|
+
*/
|
|
142
|
+
private _extractPeerAddress;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=xmtp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xmtp.d.ts","sourceRoot":"","sources":["../../src/messaging/xmtp.ts"],"names":[],"mappings":"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"}
|