# Balance and Sync Callbacks

RAILGUN private balances update continuously as new merkle tree commitments are received.&#x20;

This process involves two callback events: `onMerkletreeScanCallback` and `onBalanceUpdateCallback`. You can set these callbacks right after the Engine initialization from [Step 1 of the Getting Started](/developer-guide/wallet/getting-started/5.-start-the-railgun-privacy-engine.md) section.

#### Example: Set callbacks for scan progress and balance updates

```typescript
import {
  setOnUTXOMerkletreeScanCallback,
  setOnTXIDMerkletreeScanCallback,
  setOnBalanceUpdateCallback,
  refreshBalances,
} from "@railgun-community/wallet";
import {
  delay,
  MerkletreeScanUpdateEvent,
  NETWORK_CONFIG,
  NetworkName,
  RailgunBalancesEvent,
  RailgunWalletBalanceBucket,
} from "@railgun-community/shared-models";
import { TEST_NETWORK } from "../utils/constants";
import { Wallet } from "ethers";


import dotenv from "dotenv";
dotenv.config();

const BALANCE_POLLER_INTERVAL = 1000 * 60 * 1; // 1 min

/**
 * Example Callback function that gets invoked during a private balance scan process.
 * This function handles updates on the progress and status of the UTXO Merkletree scan.
 *
 * @param eventData - The event data containing information about the scan progress
 * @param eventData.progress - The current progress of the scan (typically a number between 0 and 1)
 * @param eventData.scanStatus - The current status of the scan process
 *
 */
const onUTXOMerkletreeScanCallback = (eventData: MerkletreeScanUpdateEvent) => {
  // Will get called throughout a private balance scan.
  // Handle updates on scan progress and status here, i.e. progress bar or loading indicator in the UI.
  console.log("UTXO scan update:", eventData.progress, eventData.scanStatus);
};

/**
 * Example Callback function for handling Merkle tree scan updates during a private balance scan.
 * This function is invoked with status updates throughout the scanning process.
 *
 * @param {MerkletreeScanUpdateEvent} eventData - The event data containing scan progress information
 * @param {number} eventData.progress - The progress percentage of the scan (0-100)
 * @param {string} eventData.scanStatus - The current status of the scan
 *
 */
const onTXIDMerkletreeScanCallback = (eventData: MerkletreeScanUpdateEvent) => {
  // Will get called throughout a private balance scan.
  // Handle updates on scan progress and status here, i.e. progress bar or loading indicator in the UI.
  console.log("TXID scan update:", eventData.progress, eventData.scanStatus);
};

/**
 * Callback function triggered when a RAILGUN wallet's balance is updated.
 *
 * This function is called at the end of a private balance scan for a RAILGUN wallet
 * for each txidVersion and balanceBucket. The function logs balance updates and
 * stores the updated balances in a cache.
 *
 * @param balancesFormatted - The updated balances event containing:
 *   - txidVersion: Currently only V2_PoseidonMerkle, with V3_PoseidonMerkle planned for future
 *   - chain: The blockchain network
 *   - railgunWalletID: Unique identifier for the RAILGUN wallet
 *   - balanceBucket: Category of funds based on the Private POI system status:
 *     - "Spendable": Funds available for use in private DeFi interactions
 *     - "ShieldBlocked": Funds that are blocked from being shielded
 *     - "ShieldPending": Funds in the process of being shielded
 *     - "ProofSubmitted": Funds for which proofs have been submitted
 *     - "MissingInternalPOI": Funds missing internal proof of innocence
 *     - "MissingExternalPOI": Funds missing external proof of innocence
 *     - "Spent": Funds that have been spent
 *   - erc20Amounts: Array of ERC20 token amounts in the wallet
 *   - nftAmounts: Array of NFT amounts in the wallet
 */
const onBalanceUpdateCallback = (balancesFormatted: RailgunBalancesEvent) => {
  // Will get called at the end of a private balance scan for a RAILGUN wallet
  // for each txidVersion and balanceBucket (explained below).
  // RailgunBalancesEvent includes:
  // *txidVersion: TXIDVersion;
  // chain: Chain;
  // railgunWalletID: string;
  // *balanceBucket: RailgunWalletBalanceBucket;
  // erc20Amounts: RailgunERC20Amount[];
  // nftAmounts: RailgunNFTAmount[];
  // *txidVersion: Currently, there is only the V2_PoseidonMerkle txidVersion. In the future,
  // with the launch of V3_PoseidonMerkle, there will be options to migrate balances
  // from V2 to V3.
  // *balanceBucket: With the Private Proof of Innocence system, balances are categorized
  // into "Spendable", "ShieldBlocked", "ShieldPending", "ProofSubmitted", "MissingInternalPOI",
  // "MissingExternalPOI", and "Spent". As funds move through the Private POI system, as
  // explained here: https://docs.railgun.org/wiki/assurance/private-proofs-of-innocence,
  // they will automatically end up in the "Spendable" category, which is when they are
  // able to be used in private DeFi interactions.
  // Handle updates on the private token balances of each available RAILGUN wallet here.
  console.log("Balances updated:", balancesFormatted.balanceBucket);
  if (balancesFormatted.erc20Amounts.length > 0) {
    console.log("ERC20 Balances: ", balancesFormatted.erc20Amounts);
  }
  balanceCache.set(balancesFormatted.balanceBucket, balancesFormatted);
};

/**
 * Sets up callbacks for balance-related operations.
 *
 * This function configures three callbacks:
 * 1. UTXO Merkletree scanning callback
 * 2. TXID Merkletree scanning callback
 * 3. Balance update callback
 *
 * These callbacks are essential for tracking and managing wallet balance updates
 * and scanning operations in the Railgun privacy system.
 *
 * @returns {void}
 */
export const setupBalanceCallbacks = () => {
  setOnUTXOMerkletreeScanCallback(onUTXOMerkletreeScanCallback);
  setOnTXIDMerkletreeScanCallback(onTXIDMerkletreeScanCallback);
  setOnBalanceUpdateCallback(onBalanceUpdateCallback);
};

/**
 * Polls for balance updates for specified RAILGUN wallets at regular intervals.
 *
 * This function starts the private balance scan for all provided RAILGUN wallet IDs.
 * It runs in the background and triggers the appropriate callbacks as the scan progresses.
 * When the scan completes, it calls the onBalanceUpdateCallback.
 * After completion, it waits for a specified interval before polling again recursively.
 *
 * @param walletIds - Array of RAILGUN wallet IDs to poll balances for
 * @returns A Promise that resolves when the current polling iteration is complete
 *
 * @remarks
 * - Uses the TEST_NETWORK configuration to determine the chain
 * - Automatically retries on error
 * - Will resolve the balanceLoadedPromise with "Loaded Balances" if it exists
 * - Waits for BALANCE_POLLER_INTERVAL milliseconds between polling iterations
 */
export const runBalancePoller = async (walletIds: string[]) => {
  // Run this function to start the private balance scan for all RAILGUN wallets.
  // This function will run in the background and call the callbacks above as the scan progresses.
  // It will also call the onBalanceUpdateCallback when the scan is complete.

  const chain = NETWORK_CONFIG[TEST_NETWORK].chain;
  console.log("Running balance poller... on chain", chain);
  try {
    // scan for all wallets.

    await refreshBalances(chain, walletIds);
  } catch (error) {
    console.error("BALANCE REFRESH ERROR", error);
    await refreshBalances(chain, walletIds);
  }
  console.log("Balance poller complete. Waiting for next poll...");
  if (balanceLoadedPromise != null) {
    balanceLoadedPromise("Loaded Balances");
    balanceLoadedPromise = undefined;
  }
  await delay(BALANCE_POLLER_INTERVAL);
  runBalancePoller(walletIds);
};

let balanceLoadedPromise: ((value: unknown) => void) | null | undefined =
  undefined;

/**
 * Waits for balances to be loaded by returning a Promise that resolves
 * when the balanceLoadedPromise is resolved elsewhere in the application.
 *
 * @returns {Promise<void>} A Promise that resolves when balances are loaded
 *
 * @example
 * await waitForBalancesLoaded();
 * // Now you can safely access balances
 */
export const waitForBalancesLoaded = async () => {
  return new Promise((resolve) => {
    balanceLoadedPromise = resolve;
  });
};

/**
 * Cache for storing RAILGUN wallet balance events.
 * This map uses {@link RailgunWalletBalanceBucket} as keys and stores {@link RailgunBalancesEvent} as values.
 * The cache helps reduce redundant balance lookups by storing the most recent balance events for each wallet bucket.
 */
export const balanceCache = new Map<
  RailgunWalletBalanceBucket,
  RailgunBalancesEvent
>();

/**
 * Retrieves the spendable balances from the balance cache.
 *
 * This function accesses the RAILGUN wallet's spendable balances from the cache.
 * Spendable balances are funds that are available for immediate use in transactions.
 *
 * @returns {Object} The spendable balances retrieved from the balance cache.
 *
 * @example
 * const balances = getSpendableBalances();
 * console.log(balances);
 */
export const getSpendableBalances = () => {
  return balanceCache.get(RailgunWalletBalanceBucket.Spendable);
};

/**
 * Displays the spendable balances of ERC20 tokens available to the wallet.
 *
 * This function retrieves spendable balances using the `getSpendableBalances()` function
 * and logs each ERC20 token balance to the console. If no balances are found,
 * it logs a message indicating that no spendable balances were found.
 *
 * @example
 * // Display all spendable token balances in the wallet
 * displaySpendableBalances();
 *
 * @returns {void}
 */
export const displaySpendableBalances = () => {
  const balances = getSpendableBalances();

  if (balances) {
    for (const erc20Amount of balances.erc20Amounts) {
      console.log("ERC20 Balance: ", erc20Amount);
    }
  } else {
    console.log("No spendable balances found.");
  }
};
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.railgun.org/developer-guide/wallet/private-balances/balance-and-sync-callbacks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
