Skip to main content

Overview

The usePortfolio hook fetches comprehensive portfolio data for a Stacks wallet address. It automatically refreshes every 30 seconds to keep balances and transaction history up-to-date. Source: src/hooks/usePortfolio.js

Import

import { usePortfolio } from './hooks/usePortfolio';

Hook Signature

function usePortfolio(address: string | null): {
  portfolio: {
    stxBalance: string,
    sbtcBalance: string,
    totalUSD: string,
    txHistory: Array,
    stxPrice: number,
  },
  loading: boolean,
  error: string | null,
}

Parameters

address
string | null
required
The Stacks wallet address to fetch portfolio data for. If null or undefined, the hook will not fetch data.Example: "SP2H8PY27SEZ03MWRKS5XABZYQN17ETGQS3527SA5"

Return Values

portfolio
object
Complete portfolio data object containing all wallet information:
portfolio.stxBalance
string
STX token balance formatted as a string (e.g., "100.5000"). Shows "--" while loading or if fetch fails.
portfolio.sbtcBalance
string
sBTC token balance formatted as a string with full precision (e.g., "0.00500000"). Shows "--" while loading.
portfolio.totalUSD
string
Total portfolio value in USD (e.g., "285.50"). Calculated from STX and sBTC balances at current market prices. Shows "--" while loading.
portfolio.txHistory
array
Array of recent transaction objects. Each transaction includes:
  • txid: Transaction hash
  • tx_status: "success", "pending", or "failed"
  • tx_type: Type of transaction (e.g., "token_transfer", "contract_call")
  • sender_address: Address that initiated the transaction
  • block_height: Block number (if confirmed)
  • burn_block_time_iso: Timestamp in ISO format
Empty array [] while loading or if no transactions found.
portfolio.stxPrice
number
Current STX price in USD (e.g., 2.85). Used to calculate totalUSD. Defaults to 0 while loading.
loading
boolean
true during initial fetch or refresh, false once data is loaded. Use this to show loading skeletons.
error
string | null
Error message if portfolio fetch fails (e.g., "Failed to load portfolio data"). null if no error.

Usage Example

import { useWallet } from './hooks/useWallet';
import { usePortfolio } from './hooks/usePortfolio';

function PortfolioDisplay() {
  const { address } = useWallet();
  const { portfolio, loading, error } = usePortfolio(address);

  if (loading) {
    return <div>Loading portfolio...</div>;
  }

  if (error) {
    return <div className="error">{error}</div>;
  }

  return (
    <div className="portfolio">
      <h2>Your Portfolio</h2>
      <p>STX Balance: {portfolio.stxBalance} STX</p>
      <p>sBTC Balance: {portfolio.sbtcBalance} sBTC</p>
      <p>Total Value: ${portfolio.totalUSD}</p>
      <p>STX Price: ${portfolio.stxPrice}</p>
    </div>
  );
}

Auto-Refresh Behavior

The hook automatically refreshes portfolio data every 30 seconds:
useEffect(() => {
  if (!address) return;

  async function fetchPortfolio() {
    // Fetch logic...
  }

  fetchPortfolio(); // Initial fetch

  const interval = setInterval(fetchPortfolio, 30000); // Refresh every 30s
  return () => clearInterval(interval); // Cleanup on unmount
}, [address]);
The auto-refresh ensures users always see up-to-date balances without manual page refreshes. This is especially useful for monitoring pending transactions or deposit confirmations.

Loading States

The hook provides clear loading states throughout the data lifecycle:
1

Initial State

Before address is provided:
  • loading is false
  • All portfolio values show "--"
  • txHistory is empty []
2

Fetching State

While fetching data:
  • loading is true
  • Previous portfolio values remain visible (or "--" on first load)
  • UI should show loading indicators
3

Success State

After successful fetch:
  • loading is false
  • All portfolio values are populated
  • error is null
4

Error State

If fetch fails:
  • loading is false
  • error contains error message
  • Portfolio values show "--"

Error Handling

The hook catches and formats errors from the Stacks API:
try {
  setLoading(true);
  setError(null);
  const data = await getFullPortfolio(address);
  setPortfolio(data);
} catch (err) {
  setError('Failed to load portfolio data');
  console.error(err);
} finally {
  setLoading(false);
}
Common errors:
  • Network request timeout
  • Invalid address format
  • Stacks API rate limiting
  • Blockchain node unavailable

Data Format

Portfolio data comes from the getFullPortfolio service:
import { getFullPortfolio } from '../services/stacksApi';

// Returns:
{
  stxBalance: "100.5000",
  sbtcBalance: "0.00500000",
  totalUSD: "285.50",
  stxPrice: 2.85,
  txHistory: [
    {
      txid: "0x1234...",
      tx_status: "success",
      tx_type: "token_transfer",
      sender_address: "SP2H8...",
      block_height: 123456,
      burn_block_time_iso: "2024-03-09T12:00:00.000Z",
    },
    // ... more transactions
  ]
}

Best Practices

Check Address

Always ensure address is valid before rendering portfolio data. Show a connect prompt if null.

Loading Skeletons

Use loading state to show skeleton loaders instead of empty states during fetch.

Error Display

Always display the error message to users when portfolio fetch fails. Include a retry option.

Auto-Refresh Awareness

Remember data refreshes every 30 seconds. Consider showing a “last updated” timestamp.

Common Patterns

Conditional Rendering Based on Connection

function Portfolio() {
  const { connected, address } = useWallet();
  const { portfolio, loading } = usePortfolio(connected ? address : null);
  
  if (!connected) {
    return <ConnectPrompt />;
  }
  
  if (loading) {
    return <LoadingSkeleton />;
  }
  
  return <PortfolioDisplay data={portfolio} />;
}

Passing Portfolio to Other Hooks

function Dashboard() {
  const { address } = useWallet();
  const { portfolio } = usePortfolio(address);
  
  // Pass portfolio data to AI advisor
  const { strategy } = useAIAdvisor({
    address,
    stxBalance: portfolio.stxBalance,
    sbtcBalance: portfolio.sbtcBalance,
    totalUSD: portfolio.totalUSD,
  });
  
  return <div>{/* ... */}</div>;
}

Manual Refresh Trigger

import { useState, useEffect } from 'react';
import { getFullPortfolio } from './services/stacksApi';

function PortfolioWithRefresh({ address }) {
  const { portfolio, loading } = usePortfolio(address);
  const [refreshing, setRefreshing] = useState(false);
  
  async function handleManualRefresh() {
    setRefreshing(true);
    // Force immediate refresh by calling API directly
    await getFullPortfolio(address);
    setRefreshing(false);
  }
  
  return (
    <div>
      <button onClick={handleManualRefresh} disabled={refreshing}>
        {refreshing ? 'Refreshing...' : 'Refresh Now'}
      </button>
      {/* Portfolio display */}
    </div>
  );
}

Performance Considerations

The hook refreshes every 30 seconds. If you have multiple components using usePortfolio with the same address, consider lifting the hook to a parent component and passing data as props to avoid duplicate API calls.
// Good: Single hook instance
function App() {
  const { address } = useWallet();
  const { portfolio, loading } = usePortfolio(address);
  
  return (
    <div>
      <PortfolioHeader data={portfolio} loading={loading} />
      <PortfolioCards data={portfolio} loading={loading} />
      <AIAdvisor portfolio={portfolio} />
    </div>
  );
}

// Bad: Multiple hook instances (3x API calls)
function App() {
  return (
    <div>
      <PortfolioHeader /> {/* calls usePortfolio */}
      <PortfolioCards /> {/* calls usePortfolio */}
      <AIAdvisor /> {/* calls usePortfolio */}
    </div>
  );
}
  • Stacks API - Underlying portfolio data service
  • AI Service - Uses portfolio data for strategy generation

Build docs developers (and LLMs) love