0
# Subscription Management
1
2
The Web3 subscription management system provides real-time WebSocket connectivity for blockchain events with automatic reconnection, event handling, and comprehensive subscription lifecycle management. It enables applications to receive live updates for blockchain changes.
3
4
## Capabilities
5
6
### Web3SubscriptionManager Class
7
8
Central manager for handling multiple WebSocket subscriptions with lifecycle management and event coordination.
9
10
```typescript { .api }
11
/**
12
* Manages WebSocket subscriptions to blockchain events with lifecycle management
13
* @template API - JSON-RPC API specification type
14
* @template RegisteredSubs - Registered subscription constructor types
15
*/
16
class Web3SubscriptionManager<
17
API extends Web3APISpec = Web3APISpec,
18
RegisteredSubs extends {[key: string]: Web3SubscriptionConstructor} = {[key: string]: Web3SubscriptionConstructor}
19
> {
20
constructor(
21
requestManager: Web3RequestManager<API>,
22
registeredSubscriptions: RegisteredSubs,
23
tolerateUnlinkedSubscription?: boolean
24
);
25
26
// Core properties
27
readonly requestManager: Web3RequestManager<API>;
28
readonly registeredSubscriptions: RegisteredSubs;
29
readonly subscriptions: Map<string, InstanceType<RegisteredSubs[keyof RegisteredSubs]>>;
30
31
// Subscription management
32
subscribe<T extends keyof RegisteredSubs>(
33
name: T,
34
args?: ConstructorParameters<RegisteredSubs[T]>[0],
35
returnFormat?: DataFormat
36
): Promise<InstanceType<RegisteredSubs[T]>>;
37
38
addSubscription(
39
sub: InstanceType<RegisteredSubs[keyof RegisteredSubs]>
40
): Promise<string>;
41
42
removeSubscription(
43
sub: InstanceType<RegisteredSubs[keyof RegisteredSubs]>
44
): Promise<string>;
45
46
unsubscribe(condition?: ShouldUnsubscribeCondition): Promise<string[]>;
47
48
clear(): void;
49
50
// Capability detection
51
supportsSubscriptions(): boolean;
52
}
53
```
54
55
**Usage Examples:**
56
57
```typescript
58
import { Web3SubscriptionManager, Web3RequestManager } from "web3-core";
59
import { EthExecutionAPI } from "web3-types";
60
61
// Define subscription types
62
interface MySubscriptions {
63
newBlockHeaders: typeof NewBlockHeadersSubscription;
64
pendingTransactions: typeof PendingTransactionsSubscription;
65
logs: typeof LogsSubscription;
66
}
67
68
// Create WebSocket request manager
69
const wsRequestManager = new Web3RequestManager<EthExecutionAPI>(
70
"wss://eth-mainnet.ws.alchemyapi.io/v2/your-api-key"
71
);
72
73
// Create subscription manager
74
const subscriptionManager = new Web3SubscriptionManager(
75
wsRequestManager,
76
{
77
newBlockHeaders: NewBlockHeadersSubscription,
78
pendingTransactions: PendingTransactionsSubscription,
79
logs: LogsSubscription
80
} as MySubscriptions
81
);
82
83
// Check subscription support
84
if (subscriptionManager.supportsSubscriptions()) {
85
console.log("WebSocket provider supports subscriptions");
86
87
// Subscribe to new block headers
88
const blockSubscription = await subscriptionManager.subscribe("newBlockHeaders");
89
90
blockSubscription.on("data", (blockHeader) => {
91
console.log("New block:", blockHeader.number, blockHeader.hash);
92
});
93
94
blockSubscription.on("error", (error) => {
95
console.error("Block subscription error:", error);
96
});
97
} else {
98
console.log("Current provider does not support subscriptions");
99
}
100
```
101
102
### Web3Subscription Base Class
103
104
Abstract base class for implementing specific subscription types with event handling and lifecycle management.
105
106
```typescript { .api }
107
/**
108
* Abstract base class for WebSocket subscriptions with event emission
109
* @template EventMap - Event type mapping for this subscription
110
* @template ArgsType - Constructor arguments type
111
* @template API - JSON-RPC API specification type
112
*/
113
abstract class Web3Subscription<
114
EventMap extends {[key: string]: unknown} = {[key: string]: unknown},
115
ArgsType = unknown,
116
API extends Web3APISpec = Web3APISpec
117
> extends Web3EventEmitter<EventMap & {
118
data: any;
119
changed: any;
120
error: Error;
121
connected: string;
122
disconnected: string | Error;
123
}> {
124
constructor(
125
args: ArgsType,
126
options: {
127
subscriptionManager: Web3SubscriptionManager<API, any>;
128
returnFormat?: DataFormat;
129
} | {
130
requestManager: Web3RequestManager<API>;
131
returnFormat?: DataFormat;
132
}
133
);
134
135
// Core properties
136
readonly id?: HexString;
137
readonly args: ArgsType;
138
lastBlock?: BlockOutput;
139
readonly returnFormat: DataFormat;
140
141
// Subscription lifecycle
142
subscribe(): Promise<string>;
143
unsubscribe(): Promise<void>;
144
resubscribe(): Promise<void>;
145
146
// Internal methods (implement in subclasses)
147
protected abstract sendSubscriptionRequest(): Promise<string>;
148
protected abstract sendUnsubscribeRequest(): Promise<void>;
149
protected abstract processSubscriptionData(
150
data: JsonRpcSubscriptionResult | JsonRpcSubscriptionResultOld<Log> | JsonRpcNotification<Log>
151
): void;
152
protected abstract formatSubscriptionResult(data: any): any;
153
}
154
```
155
156
**Usage Examples:**
157
158
```typescript
159
// Example custom subscription implementation
160
class CustomEventSubscription extends Web3Subscription<{
161
data: CustomEvent;
162
error: Error;
163
}> {
164
constructor(
165
args: { address: string; topics: string[] },
166
options: { subscriptionManager: Web3SubscriptionManager }
167
) {
168
super(args, options);
169
}
170
171
protected async sendSubscriptionRequest(): Promise<string> {
172
return this.requestManager.send({
173
method: "eth_subscribe",
174
params: ["logs", {
175
address: this.args.address,
176
topics: this.args.topics
177
}]
178
});
179
}
180
181
protected async sendUnsubscribeRequest(): Promise<void> {
182
if (this.id) {
183
await this.requestManager.send({
184
method: "eth_unsubscribe",
185
params: [this.id]
186
});
187
}
188
}
189
190
protected processSubscriptionData(data: JsonRpcSubscriptionResult): void {
191
const formattedData = this.formatSubscriptionResult(data.params.result);
192
this.emit("data", formattedData);
193
}
194
195
protected formatSubscriptionResult(data: any): CustomEvent {
196
// Format the raw data into CustomEvent format
197
return {
198
address: data.address,
199
topics: data.topics,
200
data: data.data,
201
blockNumber: data.blockNumber,
202
transactionHash: data.transactionHash
203
};
204
}
205
}
206
207
// Use custom subscription
208
const customSub = new CustomEventSubscription(
209
{
210
address: "0x742d35Cc6634C0532925a3b8D0d3",
211
topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
212
},
213
{ subscriptionManager }
214
);
215
216
await customSub.subscribe();
217
218
customSub.on("data", (event) => {
219
console.log("Custom event received:", event);
220
});
221
```
222
223
### Subscription Lifecycle Management
224
225
Methods for managing subscription states and handling connection events.
226
227
```typescript { .api }
228
/**
229
* Subscribe to blockchain events
230
* @returns Promise resolving to subscription ID
231
*/
232
subscribe(): Promise<string>;
233
234
/**
235
* Unsubscribe from blockchain events
236
* @returns Promise resolving when unsubscribed
237
*/
238
unsubscribe(): Promise<void>;
239
240
/**
241
* Resubscribe to blockchain events (useful after connection loss)
242
* @returns Promise resolving when resubscribed
243
*/
244
resubscribe(): Promise<void>;
245
246
/**
247
* Add existing subscription instance to manager
248
* @param sub - Subscription instance to add
249
* @returns Promise resolving to subscription ID
250
*/
251
addSubscription(sub: InstanceType<RegisteredSubs[keyof RegisteredSubs]>): Promise<string>;
252
253
/**
254
* Remove subscription instance from manager
255
* @param sub - Subscription instance to remove
256
* @returns Promise resolving to removed subscription ID
257
*/
258
removeSubscription(sub: InstanceType<RegisteredSubs[keyof RegisteredSubs]>): Promise<string>;
259
260
/**
261
* Condition function type for selective unsubscription
262
*/
263
type ShouldUnsubscribeCondition = (subscription: Web3Subscription) => boolean;
264
265
/**
266
* Unsubscribe multiple subscriptions based on condition
267
* @param condition - Optional filter function for selective unsubscription
268
* @returns Promise resolving to array of unsubscribed subscription IDs
269
*/
270
unsubscribe(condition?: ShouldUnsubscribeCondition): Promise<string[]>;
271
272
/**
273
* Clear all subscriptions without sending unsubscribe requests
274
*/
275
clear(): void;
276
```
277
278
**Usage Examples:**
279
280
```typescript
281
// Basic subscription lifecycle
282
const blockSub = await subscriptionManager.subscribe("newBlockHeaders");
283
console.log("Subscribed with ID:", blockSub.id);
284
285
// Later unsubscribe
286
await blockSub.unsubscribe();
287
console.log("Unsubscribed");
288
289
// Resubscribe after connection issues
290
await blockSub.resubscribe();
291
console.log("Resubscribed with new ID:", blockSub.id);
292
293
// Selective unsubscription
294
await subscriptionManager.unsubscribe((sub) => {
295
// Unsubscribe all subscriptions older than 1 hour
296
const oneHourAgo = Date.now() - 60 * 60 * 1000;
297
return sub.createdAt < oneHourAgo;
298
});
299
300
// Clear all subscriptions (emergency cleanup)
301
subscriptionManager.clear();
302
```
303
304
### Subscription Events
305
306
Event system for handling subscription data, errors, and connection states.
307
308
```typescript { .api }
309
/**
310
* Standard subscription events
311
*/
312
interface SubscriptionEventMap {
313
/**
314
* Emitted when new data is received
315
*/
316
data: any;
317
318
/**
319
* Emitted when subscription data changes
320
*/
321
changed: any;
322
323
/**
324
* Emitted when subscription encounters an error
325
*/
326
error: Error;
327
328
/**
329
* Emitted when subscription successfully connects
330
*/
331
connected: string;
332
333
/**
334
* Emitted when subscription disconnects
335
*/
336
disconnected: string | Error;
337
}
338
```
339
340
**Usage Examples:**
341
342
```typescript
343
// Handle all subscription events
344
const subscription = await subscriptionManager.subscribe("newBlockHeaders");
345
346
subscription.on("data", (blockHeader) => {
347
console.log("New block header:", {
348
number: blockHeader.number,
349
hash: blockHeader.hash,
350
timestamp: blockHeader.timestamp,
351
parentHash: blockHeader.parentHash
352
});
353
});
354
355
subscription.on("changed", (changedData) => {
356
console.log("Subscription data changed:", changedData);
357
});
358
359
subscription.on("error", (error) => {
360
console.error("Subscription error:", error.message);
361
362
// Attempt to resubscribe on error
363
setTimeout(async () => {
364
try {
365
await subscription.resubscribe();
366
console.log("Successfully resubscribed after error");
367
} catch (resubError) {
368
console.error("Failed to resubscribe:", resubError);
369
}
370
}, 5000);
371
});
372
373
subscription.on("connected", (subscriptionId) => {
374
console.log("Subscription connected with ID:", subscriptionId);
375
});
376
377
subscription.on("disconnected", (reason) => {
378
if (reason instanceof Error) {
379
console.error("Subscription disconnected due to error:", reason.message);
380
} else {
381
console.log("Subscription disconnected with ID:", reason);
382
}
383
});
384
```
385
386
### Provider Capability Detection
387
388
Methods for detecting WebSocket subscription support in providers.
389
390
```typescript { .api }
391
/**
392
* Check if current provider supports subscriptions
393
* @returns Boolean indicating subscription support
394
*/
395
supportsSubscriptions(): boolean;
396
```
397
398
**Usage Examples:**
399
400
```typescript
401
// Check provider capabilities before subscribing
402
if (subscriptionManager.supportsSubscriptions()) {
403
// Provider supports subscriptions - proceed with WebSocket subscriptions
404
const logsSubscription = await subscriptionManager.subscribe("logs", {
405
address: "0x742d35Cc6634C0532925a3b8D0d3",
406
topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
407
});
408
409
logsSubscription.on("data", (log) => {
410
console.log("Token transfer:", log);
411
});
412
} else {
413
// Fall back to polling mechanism
414
console.log("Provider doesn't support subscriptions, using polling...");
415
416
const pollForBlocks = async () => {
417
try {
418
const latestBlock = await subscriptionManager.requestManager.send({
419
method: "eth_getBlockByNumber",
420
params: ["latest", false]
421
});
422
console.log("Latest block (polling):", latestBlock.number);
423
} catch (error) {
424
console.error("Polling error:", error);
425
}
426
};
427
428
// Poll every 12 seconds (average block time)
429
setInterval(pollForBlocks, 12000);
430
}
431
```
432
433
### Subscription Constructor Types
434
435
Type definitions for subscription constructors and registration.
436
437
```typescript { .api }
438
/**
439
* Constructor type for Web3 subscription classes
440
*/
441
type Web3SubscriptionConstructor<
442
EventMap = {[key: string]: unknown},
443
ArgsType = unknown
444
> = new (
445
args: ArgsType,
446
options: {
447
subscriptionManager?: Web3SubscriptionManager;
448
requestManager?: Web3RequestManager;
449
returnFormat?: DataFormat;
450
}
451
) => Web3Subscription<EventMap, ArgsType>;
452
```
453
454
**Usage Examples:**
455
456
```typescript
457
// Define subscription registry with proper typing
458
interface EthereumSubscriptions {
459
newBlockHeaders: Web3SubscriptionConstructor<
460
{ data: BlockHeader },
461
undefined
462
>;
463
logs: Web3SubscriptionConstructor<
464
{ data: Log },
465
{ address?: string; topics?: string[] }
466
>;
467
pendingTransactions: Web3SubscriptionConstructor<
468
{ data: string },
469
undefined
470
>;
471
syncing: Web3SubscriptionConstructor<
472
{ data: SyncingStatus },
473
undefined
474
>;
475
}
476
477
// Create typed subscription manager
478
const typedSubscriptionManager = new Web3SubscriptionManager<
479
EthExecutionAPI,
480
EthereumSubscriptions
481
>(
482
wsRequestManager,
483
{
484
newBlockHeaders: NewBlockHeadersSubscription,
485
logs: LogsSubscription,
486
pendingTransactions: PendingTransactionsSubscription,
487
syncing: SyncingSubscription
488
}
489
);
490
491
// TypeScript will provide proper type checking and autocomplete
492
const logsub = await typedSubscriptionManager.subscribe("logs", {
493
address: "0x742d35Cc6634C0532925a3b8D0d3",
494
topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
495
});
496
```