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
- Always unsubscribe - Clean up subscriptions when done to prevent memory leaks
- Use debounce/throttle - Prevent UI updates from becoming overwhelming
- Handle disconnections - Enable auto-reconnect and handle connection state changes
- Filter early - Use
filterEventsto reduce processing load - Use polling as fallback - Not all environments support WebSockets
- Batch confirmations - Use
confirmations > 1for important transactions