Guides
Balance Watching

Balance Watching

The SDK provides a polling-based subscription API for vault balance changes. Subscribe to incoming transfers, outgoing withdrawals, or spending limit resets without implementing your own polling loop.

Quick Start

Subscribe to Balance Changes

const unsub = sdk.watchBalance(
  (event) => {
    console.log(`${event.changes.length} token(s) changed`);
    for (const change of event.changes) {
      const direction = change.delta > 0n ? 'received' : 'sent';
      console.log(`${direction} ${change.token.symbol}: ${change.delta}`);
    }
  },
  { intervalMs: 10_000, emitInitial: true },
);

Handle Errors

const unsub = sdk.watchBalance(
  (event) => { /* handle changes */ },
  { intervalMs: 10_000 },
  (error) => {
    console.error('Balance poll failed:', error.message);
    // The watcher continues on error — it won't crash
  },
);

Stop Watching

// Unsubscribe when done (e.g., component unmount)
unsub();

Options

OptionTypeDefaultDescription
intervalMsnumber15000Poll interval in milliseconds
minIntervalMsnumber5000Minimum allowed interval (floor)
emitInitialbooleanfalseEmit current balances immediately on subscribe
⚠️

The minimum poll interval is 5 seconds to protect against aggressive polling. Setting intervalMs below 5000 will be clamped to 5000.


Balance Change Events

Each callback receives a BalanceChangeEvent:

interface BalanceChangeEvent {
  wormholeChainId: number;       // Chain ID
  address: string;               // Vault address
  portfolio: PortfolioBalance;   // Full current portfolio
  changes: TokenBalanceChange[]; // Only tokens that changed
  timestamp: number;             // Poll timestamp
}
 
interface TokenBalanceChange {
  token: TokenInfo;
  previousBalance: bigint;
  currentBalance: bigint;
  delta: bigint;  // positive = received, negative = sent
}

Detecting Deposits vs Withdrawals

sdk.watchBalance((event) => {
  for (const change of event.changes) {
    if (change.delta > 0n) {
      showNotification(`Received ${formatUnits(change.delta, change.token.decimals)} ${change.token.symbol}`);
    } else {
      showNotification(`Sent ${formatUnits(-change.delta, change.token.decimals)} ${change.token.symbol}`);
    }
  }
});

Detecting New Tokens

When a token appears in the portfolio for the first time, previousBalance is 0n:

sdk.watchBalance((event) => {
  for (const change of event.changes) {
    if (change.previousBalance === 0n && change.currentBalance > 0n) {
      console.log(`New token detected: ${change.token.symbol}`);
    }
  }
});

React Usage

With @veridex/react:

import { useBalance } from '@veridex/react';
 
function VaultBalance() {
  const { portfolio, loading, error, refetch } = useBalance({
    pollInterval: 10_000,
  });
 
  if (loading) return <Spinner />;
  if (error) return <p>Error: {error.message}</p>;
  if (!portfolio) return <p>No credential set</p>;
 
  return (
    <div>
      {portfolio.tokens.map(t => (
        <div key={t.token.address}>
          {t.token.symbol}: {t.formatted}
        </div>
      ))}
      <button onClick={refetch}>Refresh</button>
    </div>
  );
}

Enterprise Usage

The EnterpriseManager provides balance watching for admin dashboards:

import { EnterpriseManager } from '@veridex/sdk';
 
const enterprise = new EnterpriseManager({ sdk });
 
// Watch a specific user's vault
const unsub = enterprise.watchVaultBalance(
  10004,           // chain ID
  '0xVaultAddr',   // vault address
  (event) => {
    // Forward to webhook
    fetch('https://your-api.com/webhooks/balance', {
      method: 'POST',
      body: JSON.stringify(event),
    });
  },
  { intervalMs: 30_000 },
  (error) => alertOps('Balance watch failed', error),
);

Advanced: Direct BalanceWatcher

For full control, use BalanceWatcher directly:

import { BalanceWatcher } from '@veridex/sdk';
 
const watcher = new BalanceWatcher(
  async (chainId, address) => {
    return sdk.balance.getPortfolioBalance(chainId, address, false);
  },
);
 
const unsub = watcher.watch(
  10004, '0xVaultAddr',
  (event) => { /* ... */ },
  { intervalMs: 10_000, emitInitial: true },
);
 
// Stop all watchers at once
watcher.stopAll();

Multiple Subscribers

Multiple callbacks can subscribe to the same vault — they share one polling loop:

const unsub1 = watcher.watch(10004, '0xVault', onUIUpdate);
const unsub2 = watcher.watch(10004, '0xVault', onAnalytics);
// One poll, both callbacks invoked
 
unsub1(); // UI unsubscribes; analytics continues