Skip to main content

Portfolio & Positions

This guide covers how to view your smart account portfolio and token positions.

Overview

After borrowing, your stablecoins are held in your smart account. The SDK provides methods to:
  • View token balances
  • Check portfolio value
  • Monitor position changes

Getting Wallet Positions

const positions = await sdk.getWalletPositions();

positions.data.forEach(position => {
  const info = position.attributes.fungible_info;
  const quantity = position.attributes.quantity;

  console.log(`${info?.symbol || 'Unknown'}: ${quantity.float}`);
  console.log(`  Value: $${position.attributes.value}`);
  console.log(`  Price: $${position.attributes.price}`);
});

Filtering Positions

// Only simple positions (no complex DeFi positions)
const simplePositions = await sdk.getWalletPositions({
  filterPositions: 'only_simple'
});

// Exclude trash/dust tokens
const cleanPositions = await sdk.getWalletPositions({
  filterTrash: 'only_non_trash'
});

// Both filters
const filtered = await sdk.getWalletPositions({
  filterPositions: 'only_simple',
  filterTrash: 'only_non_trash'
});

Position Data Structure

interface WalletPosition {
  id: string;
  type: string;
  attributes: {
    parent: string | null;
    protocol: string | null;
    name: string;
    position_type: string;
    quantity: {
      int: string;          // Raw integer value
      decimals: number;     // Token decimals
      float: number;        // Human-readable amount
      numeric: string;      // Precise string representation
    };
    value: number | null;   // USD value
    price: number;          // Token price in USD
    changes: {
      absolute_1d: number | null;  // 24h change in USD
      percent_1d: number | null;   // 24h change in %
    } | null;
    fungible_info: {
      name: string;
      symbol: string;
      icon: { url: string } | null;
      flags: { verified: boolean };
      implementations: Array<{
        chain_id: string;
        address: string;
        decimals: number;
      }>;
    } | null;
    flags: {
      displayable: boolean;
      is_trash: boolean;
    };
    updated_at: string;
    updated_at_block: number;
  };
  relationships: {
    chain: { data: { type: string; id: string } };
  };
}

Getting Portfolio Summary

const portfolio = await sdk.getWalletPortfolio();

console.log('Portfolio Summary:');
console.log('  Total Positions:', portfolio.data.attributes.total.positions);
console.log('  24h Change:', portfolio.data.attributes.changes.percent_1d, '%');

// Distribution by chain
console.log('\nBy Chain:');
Object.entries(portfolio.data.attributes.positions_distribution_by_chain)
  .forEach(([chain, value]) => {
    console.log(`  ${chain}: $${value}`);
  });

// Distribution by type
console.log('\nBy Type:');
Object.entries(portfolio.data.attributes.positions_distribution_by_type)
  .forEach(([type, value]) => {
    console.log(`  ${type}: $${value}`);
  });

Portfolio Data Structure

interface WalletPortfolio {
  id: string;
  type: string;
  attributes: {
    positions_distribution_by_type: Record<string, number>;
    positions_distribution_by_chain: Record<string, number>;
    total: {
      positions: number;
    };
    changes: {
      absolute_1d: number;  // 24h change in USD
      percent_1d: number;   // 24h change in %
    };
  };
}

Common Use Cases

Display Token Balances

async function displayBalances(sdk: BorrowSDK) {
  const positions = await sdk.getWalletPositions({
    filterTrash: 'only_non_trash'
  });

  console.log('Token Balances:');
  console.log('─'.repeat(50));

  positions.data
    .filter(p => p.attributes.value && p.attributes.value > 0.01)
    .sort((a, b) => (b.attributes.value || 0) - (a.attributes.value || 0))
    .forEach(p => {
      const symbol = p.attributes.fungible_info?.symbol || 'Unknown';
      const amount = p.attributes.quantity.float.toFixed(4);
      const value = p.attributes.value?.toFixed(2) || '0.00';

      console.log(`${symbol.padEnd(10)} ${amount.padStart(15)} ($${value})`);
    });
}

Check Specific Token Balance

async function getTokenBalance(sdk: BorrowSDK, symbol: string): Promise<number> {
  const positions = await sdk.getWalletPositions();

  const token = positions.data.find(
    p => p.attributes.fungible_info?.symbol === symbol
  );

  return token?.attributes.quantity.float || 0;
}

// Usage
const usdcBalance = await getTokenBalance(sdk, 'USDC');
console.log('USDC Balance:', usdcBalance);

Calculate Total Portfolio Value

async function getTotalValue(sdk: BorrowSDK): Promise<number> {
  const positions = await sdk.getWalletPositions();

  return positions.data.reduce((total, p) => {
    return total + (p.attributes.value || 0);
  }, 0);
}

Monitor Position Changes

async function checkPositionChanges(sdk: BorrowSDK) {
  const positions = await sdk.getWalletPositions();

  const gainers: string[] = [];
  const losers: string[] = [];

  positions.data.forEach(p => {
    const change = p.attributes.changes?.percent_1d;
    if (change === null || change === undefined) return;

    const symbol = p.attributes.fungible_info?.symbol || 'Unknown';

    if (change > 0) {
      gainers.push(`${symbol}: +${change.toFixed(2)}%`);
    } else if (change < 0) {
      losers.push(`${symbol}: ${change.toFixed(2)}%`);
    }
  });

  console.log('Gainers:', gainers.join(', ') || 'None');
  console.log('Losers:', losers.join(', ') || 'None');
}

Error Handling

try {
  const positions = await sdk.getWalletPositions();
} catch (error) {
  if (error.message.includes('not initialized')) {
    console.error('Call setup() first');
    await sdk.setup();
    return await sdk.getWalletPositions();
  }
  throw error;
}

Complete Example

import { BorrowSDK, ChainType } from '@satsterminal-sdk/borrow';

async function viewPortfolio() {
  const sdk = new BorrowSDK({
    apiKey: process.env.API_KEY!,
    chain: ChainType.ARBITRUM,
    wallet: walletProvider
  });

  await sdk.setup();

  // Get portfolio summary
  const portfolio = await sdk.getWalletPortfolio();

  console.log('═'.repeat(50));
  console.log('PORTFOLIO SUMMARY');
  console.log('═'.repeat(50));
  console.log(`Total Positions: ${portfolio.data.attributes.total.positions}`);
  console.log(`24h Change: ${portfolio.data.attributes.changes.percent_1d.toFixed(2)}%`);

  // Get positions
  const positions = await sdk.getWalletPositions({
    filterTrash: 'only_non_trash'
  });

  console.log('\n' + '─'.repeat(50));
  console.log('TOKEN BALANCES');
  console.log('─'.repeat(50));

  let totalValue = 0;

  positions.data
    .filter(p => p.attributes.displayable !== false)
    .sort((a, b) => (b.attributes.value || 0) - (a.attributes.value || 0))
    .forEach(p => {
      const symbol = p.attributes.fungible_info?.symbol || 'Unknown';
      const amount = p.attributes.quantity.float;
      const value = p.attributes.value || 0;
      const change = p.attributes.changes?.percent_1d;

      totalValue += value;

      let changeStr = '';
      if (change !== null && change !== undefined) {
        changeStr = change >= 0 ? ` (+${change.toFixed(1)}%)` : ` (${change.toFixed(1)}%)`;
      }

      console.log(
        `${symbol.padEnd(8)} ${amount.toFixed(4).padStart(12)} ` +
        `$${value.toFixed(2).padStart(10)}${changeStr}`
      );
    });

  console.log('─'.repeat(50));
  console.log(`TOTAL`.padEnd(8) + `$${totalValue.toFixed(2).padStart(23)}`);
  console.log('═'.repeat(50));
}

viewPortfolio().catch(console.error);

Best Practices

1. Cache Positions for UI

// Don't fetch on every render
const [positions, setPositions] = useState(null);

useEffect(() => {
  sdk.getWalletPositions().then(setPositions);
}, []); // Fetch once on mount

// Refresh periodically
useEffect(() => {
  const interval = setInterval(() => {
    sdk.getWalletPositions().then(setPositions);
  }, 30000); // Every 30 seconds

  return () => clearInterval(interval);
}, []);

2. Filter Dust Tokens

const meaningfulPositions = positions.data.filter(
  p => (p.attributes.value || 0) > 0.01 // > $0.01
);

3. Handle Missing Data

const symbol = position.attributes.fungible_info?.symbol ?? 'Unknown';
const value = position.attributes.value ?? 0;
const change = position.attributes.changes?.percent_1d ?? null;

4. Format Numbers Appropriately

function formatAmount(amount: number, symbol: string): string {
  if (symbol === 'BTC' || symbol === 'WBTC') {
    return amount.toFixed(8);
  } else if (symbol === 'ETH') {
    return amount.toFixed(6);
  } else {
    return amount.toFixed(2);
  }
}