0
# Events and Metadata
1
2
Access runtime events, errors, and metadata for dynamic API behavior. The @polkadot/api provides comprehensive access to blockchain runtime information that enables dynamic interface generation and event monitoring.
3
4
## Capabilities
5
6
### Runtime Events
7
8
Access to all runtime events emitted by the blockchain.
9
10
```typescript { .api }
11
interface DecoratedEvents<ApiType> {
12
[section: string]: {
13
[event: string]: EventCreator;
14
};
15
}
16
17
interface EventCreator {
18
/**
19
* Create event instance
20
* @param data - Event data parameters
21
* @returns Event instance
22
*/
23
(...data: any[]): Event;
24
25
/**
26
* Check if event matches this type
27
* @param event - Event to check
28
* @returns Type guard result
29
*/
30
is(event: Event): boolean;
31
32
/** Event metadata */
33
readonly meta: EventMetadataLatest;
34
35
/** Event section name */
36
readonly section: string;
37
38
/** Event method name */
39
readonly method: string;
40
}
41
42
interface Event extends Codec {
43
/** Event section (pallet name) */
44
readonly section: string;
45
46
/** Event method name */
47
readonly method: string;
48
49
/** Event data parameters */
50
readonly data: Codec[];
51
52
/** Event metadata */
53
readonly meta: EventMetadataLatest;
54
55
/** Event index */
56
readonly index: Bytes;
57
}
58
```
59
60
### Runtime Errors
61
62
Access to all runtime errors that can be returned by extrinsics.
63
64
```typescript { .api }
65
interface DecoratedErrors<ApiType> {
66
[section: string]: {
67
[error: string]: ErrorCreator;
68
};
69
}
70
71
interface ErrorCreator {
72
/**
73
* Check if error matches this type
74
* @param error - Error to check
75
* @returns Type guard result
76
*/
77
is(error: RegistryError): boolean;
78
79
/** Error metadata */
80
readonly meta: ErrorMetadataLatest;
81
82
/** Error section name */
83
readonly section: string;
84
85
/** Error name */
86
readonly name: string;
87
}
88
89
interface RegistryError extends Codec {
90
/** Error section (pallet name) */
91
readonly section: string;
92
93
/** Error name */
94
readonly name: string;
95
96
/** Error documentation */
97
readonly docs: string[];
98
99
/** Error index */
100
readonly index: number;
101
}
102
```
103
104
### Runtime Metadata
105
106
Complete runtime metadata providing type and interface information.
107
108
```typescript { .api }
109
interface Metadata extends Codec {
110
/** Metadata version */
111
readonly version: number;
112
113
/** Runtime metadata */
114
readonly asLatest: MetadataLatest;
115
116
/** Convert to JSON */
117
toJSON(): any;
118
119
/** Convert to human-readable format */
120
toHuman(): any;
121
}
122
123
interface MetadataLatest {
124
/** Lookup registry for types */
125
readonly lookup: PortableRegistry;
126
127
/** Pallet metadata */
128
readonly pallets: Vec<PalletMetadata>;
129
130
/** Extrinsic metadata */
131
readonly extrinsic: ExtrinsicMetadata;
132
133
/** Runtime API metadata */
134
readonly apis: Vec<RuntimeApiMetadata>;
135
136
/** Outer enums (Event, Call, Error) */
137
readonly outerEnums: MetadataOuterEnums;
138
139
/** Runtime type information */
140
readonly type: SiLookupTypeId;
141
}
142
143
interface PalletMetadata {
144
/** Pallet name */
145
readonly name: string;
146
147
/** Storage metadata */
148
readonly storage?: PalletStorageMetadata;
149
150
/** Call metadata */
151
readonly calls?: PalletCallMetadata;
152
153
/** Event metadata */
154
readonly events?: PalletEventMetadata;
155
156
/** Constants metadata */
157
readonly constants: Vec<PalletConstantMetadata>;
158
159
/** Error metadata */
160
readonly errors?: PalletErrorMetadata;
161
162
/** Pallet index */
163
readonly index: number;
164
}
165
```
166
167
### Event Records
168
169
Structure for events emitted during block execution.
170
171
```typescript { .api }
172
interface EventRecord extends Codec {
173
/** Execution phase when event was emitted */
174
readonly phase: Phase;
175
176
/** The actual event */
177
readonly event: Event;
178
179
/** Event topics for filtering */
180
readonly topics: Vec<Hash>;
181
}
182
183
interface Phase extends Enum {
184
/** Emitted during block initialization */
185
readonly isApplyExtrinsic: boolean;
186
187
/** Emitted during extrinsic execution */
188
readonly asApplyExtrinsic: number;
189
190
/** Emitted during block finalization */
191
readonly isFinalization: boolean;
192
193
/** Emitted during block initialization */
194
readonly isInitialization: boolean;
195
}
196
```
197
198
### Registry and Type Information
199
200
Type registry providing runtime type definitions.
201
202
```typescript { .api }
203
interface Registry {
204
/** Chain properties */
205
readonly chainDecimals: number[];
206
readonly chainSS58: number;
207
readonly chainTokens: string[];
208
209
/**
210
* Create type instance
211
* @param type - Type name or definition
212
* @param value - Initial value
213
* @returns Type instance
214
*/
215
createType<T = Codec>(type: string, value?: any): T;
216
217
/**
218
* Get type definition
219
* @param type - Type name
220
* @returns Type definition
221
*/
222
getDefinition(type: string): string;
223
224
/**
225
* Check if type exists
226
* @param type - Type name
227
* @returns Existence check
228
*/
229
hasType(type: string): boolean;
230
231
/**
232
* Register custom types
233
* @param types - Type definitions
234
*/
235
register(types: RegistryTypes): void;
236
237
/**
238
* Set chain properties
239
* @param properties - Chain properties
240
*/
241
setChainProperties(properties: ChainProperties): void;
242
243
/**
244
* Set metadata
245
* @param metadata - Runtime metadata
246
*/
247
setMetadata(metadata: Metadata): void;
248
249
/**
250
* Find call by index
251
* @param callIndex - Call index bytes
252
* @returns Call function
253
*/
254
findMetaCall(callIndex: Uint8Array): CallFunction;
255
256
/**
257
* Find error by index
258
* @param errorIndex - Error index bytes
259
* @returns Registry error
260
*/
261
findMetaError(errorIndex: Uint8Array): RegistryError;
262
}
263
```
264
265
## Usage Examples
266
267
### Event Monitoring
268
269
```typescript
270
import { ApiPromise } from "@polkadot/api";
271
272
const api = await ApiPromise.create();
273
274
// Subscribe to all system events
275
const unsubscribe = await api.query.system.events((events) => {
276
console.log(`Received ${events.length} events:`);
277
278
events.forEach((record, index) => {
279
const { event, phase } = record;
280
const types = event.typeDef;
281
282
console.log(`\t${index}: ${event.section}.${event.method}${
283
phase.isApplyExtrinsic ? ` (extrinsic ${phase.asApplyExtrinsic})` : ''
284
}`);
285
286
// Show event data
287
event.data.forEach((data, index) => {
288
console.log(`\t\t${types[index].type}: ${data.toString()}`);
289
});
290
});
291
});
292
```
293
294
### Specific Event Filtering
295
296
```typescript
297
import { ApiPromise } from "@polkadot/api";
298
299
const api = await ApiPromise.create();
300
301
// Filter for balance transfer events
302
const unsubscribe = await api.query.system.events((events) => {
303
events.forEach(({ event, phase }) => {
304
// Check if event is a balance transfer
305
if (api.events.balances.Transfer.is(event)) {
306
const [from, to, amount] = event.data;
307
console.log(`Transfer: ${from} -> ${to}: ${amount.toHuman()}`);
308
}
309
310
// Check for transaction fees
311
if (api.events.balances.Withdraw.is(event)) {
312
const [account, amount] = event.data;
313
console.log(`Fee paid by ${account}: ${amount.toHuman()}`);
314
}
315
316
// Check for failed extrinsics
317
if (api.events.system.ExtrinsicFailed.is(event)) {
318
const [dispatchError, dispatchInfo] = event.data;
319
console.log('Extrinsic failed:', dispatchError.toString());
320
}
321
});
322
});
323
```
324
325
### Error Handling
326
327
```typescript
328
import { ApiPromise } from "@polkadot/api";
329
330
const api = await ApiPromise.create();
331
332
// Handle transaction with error checking
333
const transfer = api.tx.balances.transferAllowDeath(
334
'1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg',
335
1000000000000
336
);
337
338
const unsubscribe = await transfer.signAndSend(sender, ({ status, events, dispatchError }) => {
339
if (status.isInBlock) {
340
console.log(`Transaction included in block ${status.asInBlock}`);
341
342
if (dispatchError) {
343
if (dispatchError.isModule) {
344
// Module error - can decode using metadata
345
const decoded = api.registry.findMetaError(dispatchError.asModule);
346
const { docs, name, section } = decoded;
347
348
console.log(`Error: ${section}.${name}: ${docs.join(' ')}`);
349
350
// Check specific error types
351
if (api.errors.balances.InsufficientBalance.is(dispatchError)) {
352
console.log('Insufficient balance for transfer');
353
}
354
} else {
355
// Other error types
356
console.log('Error:', dispatchError.toString());
357
}
358
}
359
360
// Check events for additional context
361
events.forEach(({ event }) => {
362
if (api.events.system.ExtrinsicFailed.is(event)) {
363
console.log('Extrinsic failed event emitted');
364
} else if (api.events.system.ExtrinsicSuccess.is(event)) {
365
console.log('Extrinsic succeeded');
366
}
367
});
368
}
369
});
370
```
371
372
### Metadata Exploration
373
374
```typescript
375
import { ApiPromise } from "@polkadot/api";
376
377
const api = await ApiPromise.create();
378
379
// Get runtime metadata
380
const metadata = api.runtimeMetadata;
381
console.log(`Metadata version: ${metadata.version}`);
382
383
// Explore pallets
384
metadata.asLatest.pallets.forEach((pallet) => {
385
console.log(`\nPallet: ${pallet.name} (index: ${pallet.index})`);
386
387
// Storage items
388
if (pallet.storage) {
389
console.log(' Storage:');
390
pallet.storage.items.forEach((item) => {
391
console.log(` ${item.name}: ${item.type}`);
392
});
393
}
394
395
// Calls (extrinsics)
396
if (pallet.calls) {
397
console.log(' Calls:');
398
const calls = api.registry.lookup.getTypeDef(pallet.calls.type);
399
if (calls.type === 'Enum') {
400
calls.sub.forEach((call) => {
401
console.log(` ${call.name}`);
402
});
403
}
404
}
405
406
// Events
407
if (pallet.events) {
408
console.log(' Events:');
409
const events = api.registry.lookup.getTypeDef(pallet.events.type);
410
if (events.type === 'Enum') {
411
events.sub.forEach((event) => {
412
console.log(` ${event.name}`);
413
});
414
}
415
}
416
417
// Constants
418
if (pallet.constants.length > 0) {
419
console.log(' Constants:');
420
pallet.constants.forEach((constant) => {
421
const value = api.consts[pallet.name.toString()][constant.name.toString()];
422
console.log(` ${constant.name}: ${value?.toHuman() || 'N/A'}`);
423
});
424
}
425
});
426
```
427
428
### Type Registry Usage
429
430
```typescript
431
import { ApiPromise } from "@polkadot/api";
432
433
const api = await ApiPromise.create();
434
435
// Access registry
436
const registry = api.registry;
437
438
// Check chain properties
439
console.log('Chain decimals:', registry.chainDecimals);
440
console.log('Chain tokens:', registry.chainTokens);
441
console.log('SS58 format:', registry.chainSS58);
442
443
// Create custom types
444
const accountId = registry.createType('AccountId', '1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg');
445
console.log('Account ID:', accountId.toString());
446
447
const balance = registry.createType('Balance', 1000000000000);
448
console.log('Balance:', balance.toHuman());
449
450
// Type definitions
451
const hasAccountId = registry.hasType('AccountId');
452
console.log('Has AccountId type:', hasAccountId);
453
454
const accountIdDef = registry.getDefinition('AccountId');
455
console.log('AccountId definition:', accountIdDef);
456
```
457
458
### Custom Event Handling
459
460
```typescript
461
import { ApiPromise } from "@polkadot/api";
462
463
const api = await ApiPromise.create();
464
465
// Create custom event handlers
466
class EventHandler {
467
constructor(private api: ApiPromise) {}
468
469
handleBalanceEvents(events: EventRecord[]) {
470
events.forEach(({ event }) => {
471
if (this.api.events.balances.Transfer.is(event)) {
472
this.onTransfer(event.data);
473
} else if (this.api.events.balances.Deposit.is(event)) {
474
this.onDeposit(event.data);
475
} else if (this.api.events.balances.Withdraw.is(event)) {
476
this.onWithdraw(event.data);
477
}
478
});
479
}
480
481
private onTransfer([from, to, amount]: any[]) {
482
console.log(`πΈ Transfer: ${from} -> ${to}: ${amount.toHuman()}`);
483
}
484
485
private onDeposit([account, amount]: any[]) {
486
console.log(`π° Deposit: ${account}: ${amount.toHuman()}`);
487
}
488
489
private onWithdraw([account, amount]: any[]) {
490
console.log(`π³ Withdraw: ${account}: ${amount.toHuman()}`);
491
}
492
493
handleStakingEvents(events: EventRecord[]) {
494
events.forEach(({ event }) => {
495
if (this.api.events.staking.Bonded?.is(event)) {
496
const [stash, amount] = event.data;
497
console.log(`π Bonded: ${stash}: ${amount.toHuman()}`);
498
} else if (this.api.events.staking.Unbonded?.is(event)) {
499
const [stash, amount] = event.data;
500
console.log(`π Unbonded: ${stash}: ${amount.toHuman()}`);
501
} else if (this.api.events.staking.Rewarded?.is(event)) {
502
const [stash, amount] = event.data;
503
console.log(`π Reward: ${stash}: ${amount.toHuman()}`);
504
}
505
});
506
}
507
}
508
509
// Usage
510
const eventHandler = new EventHandler(api);
511
512
const unsubscribe = await api.query.system.events((events) => {
513
eventHandler.handleBalanceEvents(events);
514
eventHandler.handleStakingEvents(events);
515
});
516
```
517
518
### Runtime Version Monitoring
519
520
```typescript
521
import { ApiPromise } from "@polkadot/api";
522
523
const api = await ApiPromise.create();
524
525
// Monitor runtime version changes
526
let currentSpecVersion = api.runtimeVersion.specVersion.toNumber();
527
console.log(`Initial runtime version: ${currentSpecVersion}`);
528
529
const unsubscribe = await api.rpc.state.subscribeRuntimeVersion((version) => {
530
const newSpecVersion = version.specVersion.toNumber();
531
532
if (newSpecVersion !== currentSpecVersion) {
533
console.log(`π Runtime upgraded: ${currentSpecVersion} -> ${newSpecVersion}`);
534
console.log(`Implementation: ${version.implName}`);
535
console.log(`Spec name: ${version.specName}`);
536
537
currentSpecVersion = newSpecVersion;
538
539
// Metadata will be automatically updated
540
// You may want to refresh your application state here
541
}
542
});
543
544
// Access version information
545
const version = api.runtimeVersion;
546
console.log('Runtime info:');
547
console.log(` Spec name: ${version.specName}`);
548
console.log(` Impl name: ${version.implName}`);
549
console.log(` Spec version: ${version.specVersion}`);
550
console.log(` Impl version: ${version.implVersion}`);
551
console.log(` Transaction version: ${version.transactionVersion}`);
552
console.log(` State version: ${version.stateVersion}`);
553
```
554
555
### Event-Driven Application Logic
556
557
```typescript
558
import { ApiPromise } from "@polkadot/api";
559
560
const api = await ApiPromise.create();
561
562
// Application state manager based on events
563
class ChainStateManager {
564
private accounts = new Map<string, any>();
565
566
constructor(private api: ApiPromise) {
567
this.startEventListener();
568
}
569
570
private async startEventListener() {
571
const unsubscribe = await this.api.query.system.events((events) => {
572
events.forEach(({ event, phase }) => {
573
this.processEvent(event, phase);
574
});
575
});
576
}
577
578
private processEvent(event: Event, phase: Phase) {
579
// Process balance changes
580
if (this.api.events.balances.Transfer.is(event)) {
581
const [from, to, amount] = event.data;
582
this.updateAccountBalance(from.toString(), -amount.toBn());
583
this.updateAccountBalance(to.toString(), amount.toBn());
584
}
585
586
// Process staking events
587
if (this.api.events.staking?.Bonded?.is(event)) {
588
const [stash, amount] = event.data;
589
this.updateStakingInfo(stash.toString(), { bonded: amount.toBn() });
590
}
591
592
// Process democracy events
593
if (this.api.events.democracy?.Proposed?.is(event)) {
594
const [proposalIndex, deposit] = event.data;
595
console.log(`New proposal ${proposalIndex} with deposit ${deposit.toHuman()}`);
596
}
597
}
598
599
private updateAccountBalance(address: string, change: any) {
600
const account = this.accounts.get(address) || { balance: this.api.createType('Balance', 0) };
601
account.balance = account.balance.add(change);
602
this.accounts.set(address, account);
603
604
console.log(`Account ${address} balance: ${account.balance.toHuman()}`);
605
}
606
607
private updateStakingInfo(address: string, stakingInfo: any) {
608
const account = this.accounts.get(address) || {};
609
account.staking = { ...account.staking, ...stakingInfo };
610
this.accounts.set(address, account);
611
612
console.log(`Staking info for ${address}:`, account.staking);
613
}
614
615
getAccountInfo(address: string) {
616
return this.accounts.get(address);
617
}
618
619
getAllAccounts() {
620
return Array.from(this.accounts.entries());
621
}
622
}
623
624
// Usage
625
const stateManager = new ChainStateManager(api);
626
627
// Query account info after some time
628
setTimeout(() => {
629
const accountInfo = stateManager.getAccountInfo('1FRMM8PEiWXYax7rpS6X4XZX1aAAxSWx1CrKTyrVYhV24fg');
630
console.log('Account info:', accountInfo);
631
}, 10000);
632
```