⚠️Blast API (blastapi.io) ends Oct 31. Migrate to Dwellir and skip Alchemy's expensive compute units.
Switch Today →
Skip to main content

Coming soon: Need support for this? Email support@dwellir.com and we will enable it for you.

GraphQL Subscriptions

GraphQL subscriptions enable real-time data streaming from the Aptos blockchain, allowing applications to receive instant notifications when on-chain events occur. This feature is essential for responsive user interfaces, live dashboards, trading bots, and any application requiring immediate updates without polling.

Overview#

Unlike traditional queries that fetch data once, subscriptions establish persistent connections that stream updates as blockchain state changes. When a new block is added, transactions are processed, or specific events occur, subscribed clients receive notifications immediately, enabling truly reactive blockchain applications.

Core Subscription Patterns#

New Transactions#

subscription NewTransactions($address: String!) {
user_transactions(
where: {
_or: [
{ sender: { _eq: $address } },
{ receiver: { _eq: $address } }
]
},
order_by: { version: desc },
limit: 1
) {
hash
sender
version
success
gas_used
timestamp
}
}

Balance Changes#

subscription BalanceUpdates($owner: String!, $asset_type: String!) {
current_fungible_asset_balances(
where: {
owner_address: { _eq: $owner },
asset_type: { _eq: $asset_type }
}
) {
amount
last_transaction_version
last_transaction_timestamp
}
}

NFT Transfers#

subscription NftActivity($collection: String!) {
token_activities_v2(
where: {
token_data: {
collection_id: { _eq: $collection }
}
},
order_by: { transaction_version: desc },
limit: 1
) {
transaction_version
from_address
to_address
token_data_id
type
transaction_timestamp
}
}

New Blocks#

subscription NewBlocks {
ledger_infos(
order_by: { version: desc },
limit: 1
) {
chain_id
version
block_height
epoch
block_timestamp
}
}

Event Monitoring#

subscription EventStream($address: String!, $event_type: String!) {
events(
where: {
account_address: { _eq: $address },
type: { _eq: $event_type }
},
order_by: { transaction_version: desc },
limit: 1
) {
sequence_number
type
data
transaction_version
transaction_timestamp
}
}

Real-World Use Cases#

  1. Live Wallets: Update balances, transaction histories, and token holdings in real-time as blockchain state changes without manual refreshing.

  2. Trading Interfaces: Stream price updates, order fills, and liquidity changes instantly for responsive trading experiences on DEXes.

  3. Notification Systems: Alert users immediately when they receive payments, NFT transfers, or other important on-chain events.

  4. Live Dashboards: Display real-time protocol metrics, transaction volumes, active users, and other statistics with instant updates.

  5. Gaming Applications: Stream game state changes, item transfers, and player actions in real-time for interactive blockchain games.

  6. Monitoring Tools: Track smart contract interactions, detect anomalies, and monitor system health with instant event notifications.

Best Practices#

Handle Reconnections: Implement automatic reconnection logic with exponential backoff when subscription connections drop.

Manage Connection Limits: Be aware of concurrent subscription limits and reuse connections where possible.

Filter Aggressively: Use precise WHERE clauses to receive only relevant updates and reduce bandwidth usage.

Implement Debouncing: For rapid updates, debounce UI updates to prevent overwhelming the interface with changes.

Fallback to Polling: Have polling-based fallbacks for environments where WebSocket connections aren't available.

Validate Events: Always validate subscription payloads before processing to handle schema changes gracefully.

Monitor Performance: Track subscription latency and message rates to ensure responsive user experiences.

TypeScript Implementation#

import { ApolloClient, gql, InMemoryCache } from "@apollo/client";
import { WebSocketLink } from "@apollo/client/link/ws";
import { SubscriptionClient } from "subscriptions-transport-ws";
import ws from "ws";

// Create WebSocket client
const wsClient = new SubscriptionClient(
"wss://api-aptos-mainnet.n.dwellir.com/YOUR_API_KEY/v1/graphql",
{
reconnect: true,
connectionParams: {
headers: {
"X-API-Key": "YOUR_API_KEY"
}
}
},
ws
);

const link = new WebSocketLink(wsClient);

const client = new ApolloClient({
link,
cache: new InMemoryCache()
});

// Subscribe to transactions
function subscribeToTransactions(address: string, callback: (tx: any) => void) {
const subscription = client.subscribe({
query: gql`
subscription NewTransactions($address: String!) {
user_transactions(
where: {
_or: [
{ sender: { _eq: $address } },
{ receiver: { _eq: $address } }
]
},
order_by: { version: desc },
limit: 1
) {
hash
sender
success
timestamp
}
}
`,
variables: { address }
});

return subscription.subscribe({
next: (result) => callback(result.data.user_transactions[0]),
error: (error) => console.error("Subscription error:", error)
});
}

// Usage
const unsubscribe = subscribeToTransactions("0x123...", (transaction) => {
console.log("New transaction:", transaction);
// Update UI with new transaction
});

// Clean up
// unsubscribe();

Advanced Patterns#

Combined Updates#

// Subscribe to multiple data streams
function subscribeToWallet(address: string) {
// Transactions
const txSub = client.subscribe({
query: TRANSACTION_SUBSCRIPTION,
variables: { address }
});

// Balance changes
const balanceSub = client.subscribe({
query: BALANCE_SUBSCRIPTION,
variables: { address }
});

// NFT transfers
const nftSub = client.subscribe({
query: NFT_SUBSCRIPTION,
variables: { address }
});

return {
unsubscribe: () => {
txSub.unsubscribe();
balanceSub.unsubscribe();
nftSub.unsubscribe();
}
};
}

Conditional Subscriptions#

// Only subscribe when needed
let subscription: any = null;

function startMonitoring(address: string) {
if (subscription) return;

subscription = subscribeToTransactions(address, (tx) => {
if (tx.success) {
notifyUser(`Transaction ${tx.hash} confirmed`);
}
});
}

function stopMonitoring() {
if (subscription) {
subscription.unsubscribe();
subscription = null;
}
}

Debounced Updates#

import { debounce } from "lodash";

const updateUI = debounce((data: any) => {
// Update UI with latest data
render(data);
}, 100);

subscribeToBalances(address, (balance) => {
updateUI(balance);
});

WebSocket Connection Management#

// Robust connection handling
class SubscriptionManager {
private client: SubscriptionClient;
private subscriptions: Map<string, any> = new Map();

constructor(url: string) {
this.client = new SubscriptionClient(url, {
reconnect: true,
reconnectionAttempts: 5,
connectionParams: {
headers: { "X-API-Key": process.env.API_KEY }
}
});

this.client.onReconnected(() => {
console.log("Reconnected - resubscribing...");
this.resubscribeAll();
});
}

subscribe(id: string, query: any, variables: any, callback: Function) {
const sub = this.client.request({ query, variables }).subscribe({
next: (data) => callback(data),
error: (error) => console.error(`Subscription ${id} error:`, error)
});

this.subscriptions.set(id, { sub, query, variables, callback });
return () => this.unsubscribe(id);
}

unsubscribe(id: string) {
const subscription = this.subscriptions.get(id);
if (subscription) {
subscription.sub.unsubscribe();
this.subscriptions.delete(id);
}
}

private resubscribeAll() {
this.subscriptions.forEach((sub, id) => {
this.unsubscribe(id);
this.subscribe(id, sub.query, sub.variables, sub.callback);
});
}
}