Docs

subscriptions

Real-time subscription patterns

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

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

graphql
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

graphql
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

graphql
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

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

Event Monitoring

graphql
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

TypeScript
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

TypeScript
// 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

TypeScript
// 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

TypeScript
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

TypeScript
// 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);
    });
  }
}