Balance and Sync Callbacks
RAILGUN private balances update continuously as new merkle tree commitments are received.
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 section.
Example: Set callbacks for scan progress and balance updates
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.");
}
};
Last updated