Blog

Developer Guide

How to Track Token Velocity on Stellar for Stablecoins and RWAs

Token velocity measures how frequently an asset changes hands over a given period. It is one of the most revealing metrics for understanding whether a token is being actively used in commerce and settlement — or just sitting in wallets waiting for a price move. For stablecoins like USDC on Stellar and tokenized real-world assets (RWAs), velocity is a direct indicator of product-market fit.

What Token Velocity Is

Token velocity is calculated as:

Velocity = Transaction Volume / Average Supply in Circulation

If a stablecoin has 100 million tokens in circulation and 500 million in transaction volume over a month, the velocity is 5.0. That means each token changed hands an average of 5 times during the period.

VelocityWhat It Means
< 1.0Token is mostly held, low usage
1.0 - 5.0Moderate usage, healthy for store-of-value assets
5.0 - 20.0Active usage, typical for payment stablecoins
> 20.0Very high turnover, common for settlement tokens

Why It Matters for Stablecoins

Stablecoins are designed to be spent, not held. A USDC token sitting in a wallet for months is not doing its job. High velocity means the stablecoin is being used for:

  • Cross-border remittances
  • Merchant payments
  • DeFi settlement
  • Treasury management
  • On Stellar, USDC has over 2.1 million holders. But how many of them are actively transacting? Velocity answers that question.

    Why It Matters for RWAs

    Real-world assets on Stellar — tokenized treasury bills, bonds, real estate tokens — have a different velocity profile. You expect lower velocity (these are investment instruments), but zero velocity is a red flag:

  • Zero velocity: No secondary market activity, possibly illiquid
  • Low velocity (0.1 - 1.0): Normal for long-term holdings like bonds
  • Higher velocity: Indicates active secondary market trading
  • Fetching Payment Data from Horizon

    To calculate velocity, you need two things: transaction volume and circulating supply. Start with payments:

    const HORIZON = 'https://horizon.stellar.org';
    
    async function getPaymentVolume(assetCode, assetIssuer, hours = 24) {
      const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);
      let totalVolume = 0;
      let paymentCount = 0;
      let url = `${HORIZON}/payments?order=desc&limit=200`;
    
      while (url) {
        const res = await fetch(url);
        const data = await res.json();
        const records = data._embedded.records;
    
        let reachedCutoff = false;
        for (const record of records) {
          if (new Date(record.created_at) < cutoff) {
            reachedCutoff = true;
            break;
          }
    
          if (
            record.type === 'payment' &&
            record.asset_code === assetCode &&
            record.asset_issuer === assetIssuer
          ) {
            totalVolume += parseFloat(record.amount);
            paymentCount++;
          }
        }
    
        if (reachedCutoff || records.length < 200) break;
        url = data._links?.next?.href;
      }
    
      return { totalVolume, paymentCount, periodHours: hours };
    }

    Fetching Circulating Supply

    For Stellar assets, the circulating supply is the total amount issued minus what the issuer holds:

    async function getCirculatingSupply(assetCode, assetIssuer) {
      const res = await fetch(
        `${HORIZON}/assets?asset_code=${assetCode}&asset_issuer=${assetIssuer}`
      );
      const data = await res.json();
      const asset = data._embedded.records[0];
    
      if (!asset) return null;
    
      const totalSupply = parseFloat(asset.amount);
      const numAccounts = asset.num_accounts;
    
      // Get issuer balance to calculate circulating supply
      const issuerRes = await fetch(`${HORIZON}/accounts/${assetIssuer}`);
      const issuer = await issuerRes.json();
      const issuerBalance = issuer.balances.find(
        b => b.asset_code === assetCode && b.asset_issuer === assetIssuer
      );
      const issuerHeld = issuerBalance ? parseFloat(issuerBalance.balance) : 0;
    
      return {
        totalSupply,
        issuerHeld,
        circulating: totalSupply - issuerHeld,
        holders: numAccounts,
      };
    }

    Calculating Velocity

    async function calculateVelocity(assetCode, assetIssuer, periodHours = 24) {
      const [volume, supply] = await Promise.all([
        getPaymentVolume(assetCode, assetIssuer, periodHours),
        getCirculatingSupply(assetCode, assetIssuer),
      ]);
    
      if (!supply || supply.circulating === 0) {
        return { error: 'Cannot calculate — zero circulating supply' };
      }
    
      const annualizedVolume = volume.totalVolume * (8760 / periodHours);
      const velocity = annualizedVolume / supply.circulating;
    
      return {
        asset: `${assetCode}:${assetIssuer.slice(0, 4)}...${assetIssuer.slice(-4)}`,
        periodHours,
        transactionVolume: volume.totalVolume,
        paymentCount: volume.paymentCount,
        circulatingSupply: supply.circulating,
        holders: supply.holders,
        annualizedVelocity: Math.round(velocity * 100) / 100,
      };
    }

    Tracking Velocity Over Time

    To see trends, store daily snapshots and compare:

    async function trackVelocityTrend(assetCode, assetIssuer, days = 7) {
      const snapshots = [];
    
      for (let i = 0; i < days; i++) {
        const offsetHours = i * 24;
        // In production, you would query historical data from your database
        // This simplified version shows the concept
        const velocity = await calculateVelocity(assetCode, assetIssuer, 24);
        snapshots.push({
          date: new Date(Date.now() - offsetHours * 60 * 60 * 1000).toISOString().split('T')[0],
          velocity: velocity.annualizedVelocity,
          volume: velocity.transactionVolume,
          payments: velocity.paymentCount,
        });
      }
    
      return snapshots;
    }

    Interpreting Velocity for Specific Asset Types

    USDC on Stellar

    USDC is the most liquid stablecoin on Stellar. Expect high velocity driven by:

  • MoneyGram remittance flows
  • DEX trading pairs
  • DeFi settlement
  • A sudden velocity drop could indicate a shift in remittance corridors or reduced DEX activity.

    Tokenized Treasury Bills

    Assets like Franklin Templeton's BENJI have low velocity by design. These are buy-and-hold instruments. Watch for:

  • Velocity spikes around maturity dates
  • Gradual increase as secondary markets develop
  • Zero velocity on new issuances (holders are waiting)
  • Euro Stablecoins (EURC, EURAU)

    Euro-denominated stablecoins on Stellar target European settlement. Velocity patterns follow business hours and payment cycles. Expect higher velocity on weekdays and lower on weekends.

    Building a Velocity Dashboard

    Combine the above functions into a monitoring dashboard:

    async function buildVelocityDashboard() {
      const assets = [
        { code: 'USDC', issuer: 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN' },
        { code: 'yXLM', issuer: 'GARDNV3Q7YGT4MASTV2SUDBOMUQTTSKZYA2FKYWP45HJRYSALIDNGGEH' },
        // Add more assets as needed
      ];
    
      const results = await Promise.all(
        assets.map(a => calculateVelocity(a.code, a.issuer, 24))
      );
    
      return results.sort((a, b) => b.annualizedVelocity - a.annualizedVelocity);
    }

    Using LumenQuery Analytics

    The LumenQuery Analytics dashboard tracks token velocity, whale movements, and issuer risk for all Stellar assets. It pre-computes velocity metrics with Redis caching so you do not need to paginate through thousands of payment records yourself.

    The /api/analytics/tokens endpoint returns:

  • Payment volume and velocity for top assets
  • Whale activity (transfers > 100,000 XLM)
  • Issuer risk scores
  • Historical volume charts
  • Alerts Based on Velocity Changes

    Set up alerts for velocity anomalies:

    function checkVelocityAlert(current, previous, threshold = 0.5) {
      if (!previous) return null;
    
      const change = (current - previous) / previous;
    
      if (change > threshold) {
        return { type: 'spike', change: `+${(change * 100).toFixed(0)}%`, message: 'Unusual velocity increase' };
      }
    
      if (change < -threshold) {
        return { type: 'drop', change: `${(change * 100).toFixed(0)}%`, message: 'Significant velocity decrease' };
      }
    
      return null;
    }

    A 50% spike in velocity for a stablecoin could mean a major remittance corridor opened up. A 50% drop could indicate a liquidity problem or a migration to a different asset.

    Next Steps

  • Explore the LumenQuery Token Analytics dashboard for live velocity data
  • Read about whale tracking and large transfer monitoring
  • Check the API documentation for detailed endpoint reference

  • *Track token velocity without building infrastructure from scratch. LumenQuery provides pre-computed analytics with caching, so you can focus on insights instead of pagination. Start free.*