Skip to main content

Real-time Subscriptions

Subscribe to blockchain events in real-time using WebSockets or polling.

Overview

Cardano DevKit provides two subscription mechanisms:

  • WebSocket Subscriptions - Real-time event streaming (recommended for local devnet)
  • Polling Subscriptions - Fallback for environments without WebSocket support

Installation

import {
// WebSocket Manager
SubscriptionManager,
createSubscriptionManager,
// Polling Manager (fallback)
PollingSubscriptionManager,
createPollingSubscriptionManager,
// Transaction Confirmation
watchTxConfirmation,
// Event Aggregator
EventAggregator,
createEventAggregator,
// Callback Utilities
debounceCallback,
throttleCallback,
filterEvents,
mapEvents,
// Types
type SubscriptionEventType,
type BlockEvent,
type TransactionEvent,
type AddressEvent,
type UTxOEvent,
type SubscriptionCallback,
type SubscriptionHandle,
} from 'cardano-devkit';

WebSocket Subscriptions

Creating a Subscription Manager

import { createSubscriptionManager } from 'cardano-devkit';

const manager = createSubscriptionManager({
url: "ws://localhost:8081", // Local devnet WebSocket
autoReconnect: true,
reconnectInterval: 5000,
maxReconnectAttempts: 10,
});

// Connect to WebSocket server
await manager.connect();

Subscribing to Blocks

// New block notifications
const blockSub = manager.subscribeToBlocks((block) => {
console.log("New block:");
console.log(" Slot:", block.slot);
console.log(" Hash:", block.hash);
console.log(" Transactions:", block.txCount);
console.log(" Epoch:", block.epoch);
});

// Later: unsubscribe
blockSub.unsubscribe();

Subscribing to Address Activity

// Monitor a specific address
const addressSub = manager.subscribeToAddress(
"addr_test1qz...",
(event) => {
console.log("Address activity:");
console.log(" Type:", event.type); // 'input' | 'output'
console.log(" Tx Hash:", event.txHash);
console.log(" Amount:", event.lovelace);
if (event.assets) {
console.log(" Assets:", event.assets);
}
}
);

// Monitor multiple addresses
const multiSub = manager.subscribeToAddresses(
["addr_test1...", "addr_test2..."],
(event) => {
console.log("Activity on", event.address);
}
);

Subscribing to Transactions

// All transactions
const txSub = manager.subscribeToTransactions((tx) => {
console.log("New transaction:");
console.log(" Hash:", tx.hash);
console.log(" Block:", tx.block);
console.log(" Fee:", tx.fee);
console.log(" Inputs:", tx.inputs.length);
console.log(" Outputs:", tx.outputs.length);
});

// Transactions involving specific assets
const assetSub = manager.subscribeToAsset(
policyId,
assetName,
(event) => {
console.log("Asset movement:", event);
}
);

Subscribing to UTxO Changes

const utxoSub = manager.subscribeToUtxos(
"addr_test1...",
(event) => {
if (event.type === 'created') {
console.log("New UTxO:", event.utxo);
} else {
console.log("UTxO spent:", event.utxo);
}
}
);

Connection State

// Listen to connection state changes
manager.onConnectionChange((state) => {
console.log("Connection state:", state);
// 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
});

// Check current state
const isConnected = manager.isConnected();

// Manual reconnect
await manager.reconnect();

// Disconnect
manager.disconnect();

Polling Subscriptions

For environments without WebSocket support (or as a fallback):

import { createPollingSubscriptionManager } from 'cardano-devkit';

const pollingManager = createPollingSubscriptionManager({
provider: lucid.provider,
pollingInterval: 1000, // Check every second
batchSize: 10, // Process 10 blocks at a time
});

// Start polling
pollingManager.start();

// Subscribe to events (same API as WebSocket manager)
const blockSub = pollingManager.subscribeToBlocks((block) => {
console.log("New block:", block.slot);
});

// Stop polling
pollingManager.stop();

Transaction Confirmation

Wait for transaction confirmations with detailed progress:

import { watchTxConfirmation } from 'cardano-devkit';

// Basic usage
const confirmed = await watchTxConfirmation(lucid, txHash);
console.log("Transaction confirmed!");

// With options
const result = await watchTxConfirmation(lucid, txHash, {
confirmations: 3, // Wait for 3 confirmations
timeout: 120000, // Timeout after 2 minutes
pollingInterval: 1000, // Check every second

onConfirmation: (count, total) => {
console.log(`Confirmation ${count}/${total}`);
},

onTimeout: () => {
console.warn("Transaction confirmation timed out");
},
});

if (result.confirmed) {
console.log("Confirmed at block:", result.block);
console.log("Slot:", result.slot);
} else {
console.log("Not confirmed:", result.reason);
}

TxConfirmationConfig

interface TxConfirmationConfig {
confirmations?: number; // Default: 1
timeout?: number; // Default: 60000 (1 minute)
pollingInterval?: number; // Default: 2000 (2 seconds)
onConfirmation?: (count: number, total: number) => void;
onTimeout?: () => void;
}

Event Aggregator

Combine and process events from multiple sources:

import { createEventAggregator } from 'cardano-devkit';

const aggregator = createEventAggregator();

// Add sources
aggregator.addSource(manager);
aggregator.addSource(pollingManager);

// Aggregate events
const allBlocksSub = aggregator.subscribeToBlocks((block) => {
// Receives blocks from all sources (deduplicated)
console.log("Block from any source:", block.slot);
});

// Filter events
const filteredSub = aggregator.subscribe({
eventType: 'address',
filter: (event) => event.lovelace > 10_000_000n,
callback: (event) => {
console.log("Large transaction detected:", event);
},
});

Callback Utilities

Debounce Callbacks

Limit callback execution rate:

import { debounceCallback } from 'cardano-devkit';

const debouncedHandler = debounceCallback(
(block) => {
console.log("Block (debounced):", block.slot);
},
500 // Wait 500ms between calls
);

manager.subscribeToBlocks(debouncedHandler);

Throttle Callbacks

Ensure regular callback execution:

import { throttleCallback } from 'cardano-devkit';

const throttledHandler = throttleCallback(
(block) => {
console.log("Block (throttled):", block.slot);
},
1000 // At most once per second
);

manager.subscribeToBlocks(throttledHandler);

Filter Events

Filter events before callback:

import { filterEvents } from 'cardano-devkit';

const filteredHandler = filterEvents(
(event) => event.lovelace > 5_000_000n, // Only transactions > 5 ADA
(event) => {
console.log("Large transaction:", event);
}
);

manager.subscribeToAddress(address, filteredHandler);

Map Events

Transform events before callback:

import { mapEvents } from 'cardano-devkit';

const mappedHandler = mapEvents(
(event) => ({
...event,
adaAmount: Number(event.lovelace) / 1_000_000,
}),
(event) => {
console.log("Transaction:", event.adaAmount, "ADA");
}
);

manager.subscribeToAddress(address, mappedHandler);

Types

SubscriptionEventType

type SubscriptionEventType =
| 'block'
| 'transaction'
| 'address'
| 'utxo'
| 'delegation'
| 'reward';

BlockEvent

interface BlockEvent {
slot: number;
hash: string;
epoch: number;
height: number;
time: number;
txCount: number;
size: number;
previousHash: string;
}

TransactionEvent

interface TransactionEvent {
hash: string;
block: string;
slot: number;
fee: bigint;
inputs: TxInput[];
outputs: TxOutput[];
metadata?: TransactionMetadata;
collateral?: TxInput[];
mints?: Record<string, bigint>;
}

AddressEvent

interface AddressEvent {
address: string;
txHash: string;
type: 'input' | 'output';
lovelace: bigint;
assets?: Record<string, bigint>;
slot: number;
outputIndex?: number;
}

UTxOEvent

interface UTxOEvent {
type: 'created' | 'spent';
utxo: {
txHash: string;
outputIndex: number;
address: string;
lovelace: bigint;
assets?: Record<string, bigint>;
datum?: string;
datumHash?: string;
scriptRef?: string;
};
spentBy?: string; // Tx hash that spent this UTxO
}

ConnectionState

type ConnectionState =
| 'connecting'
| 'connected'
| 'disconnected'
| 'reconnecting'
| 'error';

SubscriptionHandle

interface SubscriptionHandle {
id: string;
unsubscribe: () => void;
pause: () => void;
resume: () => void;
isActive: () => boolean;
}

SubscriptionManagerConfig

interface SubscriptionManagerConfig {
url: string;
autoReconnect?: boolean;
reconnectInterval?: number;
maxReconnectAttempts?: number;
heartbeatInterval?: number;
debug?: boolean;
}

PollingSubscriptionConfig

interface PollingSubscriptionConfig {
provider: Provider;
pollingInterval?: number;
batchSize?: number;
startFromTip?: boolean;
}

Complete Example

import {
createDevKit,
createSubscriptionManager,
watchTxConfirmation,
debounceCallback,
} from 'cardano-devkit';

async function monitorBlockchain() {
// Initialize DevKit
const devKit = createDevKit({ network: 'LocalDevnet' });
await devKit.useLocalDevnet(10080);
const lucid = devKit.getLucid();

// Create subscription manager
const manager = createSubscriptionManager({
url: "ws://localhost:10180",
autoReconnect: true,
});

await manager.connect();
console.log("Connected to WebSocket server");

// Monitor new blocks
const blockSub = manager.subscribeToBlocks(
debounceCallback((block) => {
console.log(`Block ${block.slot} with ${block.txCount} transactions`);
}, 100)
);

// Monitor wallet address
const walletAddress = await lucid.wallet().address();
const addressSub = manager.subscribeToAddress(walletAddress, (event) => {
const ada = Number(event.lovelace) / 1_000_000;
if (event.type === 'input') {
console.log(`Sent ${ada} ADA in tx ${event.txHash}`);
} else {
console.log(`Received ${ada} ADA in tx ${event.txHash}`);
}
});

// Submit a transaction and watch for confirmation
const tx = await lucid
.newTx()
.pay.ToAddress("addr_test1...", { lovelace: 5_000_000n })
.complete();

const signedTx = await tx.sign.withWallet().complete();
const txHash = await signedTx.submit();

console.log("Submitted:", txHash);

// Watch for confirmations
await watchTxConfirmation(lucid, txHash, {
confirmations: 3,
onConfirmation: (count, total) => {
console.log(`Confirmation ${count}/${total}`);
},
});

console.log("Transaction fully confirmed!");

// Cleanup
blockSub.unsubscribe();
addressSub.unsubscribe();
manager.disconnect();
}

Best Practices

  1. Always unsubscribe - Clean up subscriptions when done to prevent memory leaks
  2. Use debounce/throttle - Prevent UI updates from becoming overwhelming
  3. Handle disconnections - Enable auto-reconnect and handle connection state changes
  4. Filter early - Use filterEvents to reduce processing load
  5. Use polling as fallback - Not all environments support WebSockets
  6. Batch confirmations - Use confirmations > 1 for important transactions