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
Copy
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
Copy
// 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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
// 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
Copy
const meaningfulPositions = positions.data.filter(
p => (p.attributes.value || 0) > 0.01 // > $0.01
);
3. Handle Missing Data
Copy
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
Copy
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);
}
}