Skip to main content

spotClearinghouseState

Get spot trading account state and token balances for a user on Hyperliquid.

Why Hyperliquid? Build on the dominant perpetuals DEX with 70% market share, $2.7T+ lifetime volume, and $2B TVL with 200K orders/second throughput, zero gas fees, sub-second finality, and fully onchain Central Limit Order Book (CLOB).

When to Use This Endpoint#

The spotClearinghouseState endpoint is essential for spot traders, portfolio managers, and trading platforms who need to:

  • Track Token Balances — Monitor available and held balances across all tokens
  • Portfolio Management — Calculate total portfolio value across spot holdings
  • Trading Validation — Verify sufficient balance before placing spot orders
  • Liquidity Management — Track which funds are locked in open orders

Request#

Endpoint#

POST https://api-hyperliquid-mainnet-info.n.dwellir.com/info

Headers#

HeaderValueRequired
Content-Typeapplication/jsonYes
X-Api-KeyYour API keyYes

Parameters#

ParameterTypeRequiredDescription
typestringYesMust be "spotClearinghouseState"
userstringYesUser's Ethereum wallet address

Example Request#

{
"type": "spotClearinghouseState",
"user": "0x63E8c7C149556D5f34F833419A287bb9Ef81487f"
}

Response#

Success Response#

{
"balances": [
{
"coin": "USDC",
"token": 0,
"total": "0.01261784",
"hold": "0.0",
"entryNtl": "0.0"
},
{
"coin": "HYPE",
"token": 150,
"total": "0.00674734",
"hold": "0.0",
"entryNtl": "0.24186439"
}
]
}

Response Fields#

FieldTypeDescription
balancesarrayArray of token balance objects

Balance Object#

FieldTypeDescription
coinstringToken symbol (e.g., "USDC", "HYPE", "ETH")
tokenintegerToken index/ID
totalstringTotal balance for this token
holdstringAmount currently held in open spot orders
entryNtlstringEntry notional value

Note: Available balance = total - hold

Code Examples#

curl -X POST 'https://api-hyperliquid-mainnet-info.n.dwellir.com/info' \
-H 'X-Api-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"type": "spotClearinghouseState",
"user": "0x63E8c7C149556D5f34F833419A287bb9Ef81487f"
}'

Common Use Cases#

1. Check Available Balance#

Calculate available balance for a specific token:

async function getAvailableBalance(userAddress, token) {
const state = await getSpotClearinghouseState(userAddress);
const balance = state.balances.find(b => b.coin === token);

if (!balance) {
return {
token: token,
available: 0,
total: 0,
hold: 0,
exists: false
};
}

const total = parseFloat(balance.total);
const hold = parseFloat(balance.hold);
const available = total - hold;

return {
token: token,
available: available,
total: total,
hold: hold,
exists: true
};
}

// Usage
const usdcBalance = await getAvailableBalance(
'0x63E8c7C149556D5f34F833419A287bb9Ef81487f',
'USDC'
);

if (usdcBalance.available < 1000) {
console.log('Insufficient USDC balance for trade');
}

2. Portfolio Value Calculator#

Calculate total portfolio value in USD:

async function calculatePortfolioValue(userAddress, prices) {
const state = await getSpotClearinghouseState(userAddress);

let totalValue = 0;
const holdings = [];

state.balances.forEach(balance => {
const total = parseFloat(balance.total);
if (total === 0) return;

const price = prices[balance.coin] || 0;
const value = total * price;
totalValue += value;

holdings.push({
token: balance.coin,
amount: total,
price: price,
value: value,
percentage: 0 // Will calculate after
});
});

// Calculate percentages
holdings.forEach(h => {
h.percentage = (h.value / totalValue) * 100;
});

// Sort by value
holdings.sort((a, b) => b.value - a.value);

return {
totalValue: totalValue,
holdings: holdings,
tokenCount: holdings.length
};
}

// Usage with example prices
const prices = { USDC: 1, ETH: 2500, BTC: 45000 };
const portfolio = await calculatePortfolioValue(
'0x63E8c7C149556D5f34F833419A287bb9Ef81487f',
prices
);

console.log(`Total Portfolio Value: $${portfolio.totalValue.toFixed(2)}`);
portfolio.holdings.forEach(h => {
console.log(`${h.token}: $${h.value.toFixed(2)} (${h.percentage.toFixed(1)}%)`);
});

3. Validate Trade Feasibility#

Check if user has sufficient balance for a trade:

async function canExecuteTrade(userAddress, token, requiredAmount) {
const balance = await getAvailableBalance(userAddress, token);

if (!balance.exists) {
return {
canTrade: false,
reason: `No ${token} balance found`,
available: 0,
required: requiredAmount
};
}

if (balance.available < requiredAmount) {
return {
canTrade: false,
reason: 'Insufficient balance',
available: balance.available,
required: requiredAmount,
shortfall: requiredAmount - balance.available
};
}

return {
canTrade: true,
available: balance.available,
required: requiredAmount,
remaining: balance.available - requiredAmount
};
}

// Usage
const tradeCheck = await canExecuteTrade(
'0x63E8c7C149556D5f34F833419A287bb9Ef81487f',
'USDC',
1000
);

if (!tradeCheck.canTrade) {
console.error(`Cannot trade: ${tradeCheck.reason}`);
console.error(`Required: ${tradeCheck.required}, Available: ${tradeCheck.available}`);
}

4. Monitor Locked Balances#

Track which tokens have funds locked in orders:

async function getLockedBalances(userAddress) {
const state = await getSpotClearinghouseState(userAddress);

const lockedBalances = state.balances
.filter(b => parseFloat(b.hold) > 0)
.map(b => ({
token: b.coin,
total: parseFloat(b.total),
hold: parseFloat(b.hold),
available: parseFloat(b.total) - parseFloat(b.hold),
percentLocked: (parseFloat(b.hold) / parseFloat(b.total)) * 100
}));

return lockedBalances;
}

// Usage
const locked = await getLockedBalances('0x63E8c7C149556D5f34F833419A287bb9Ef81487f');
console.log(`${locked.length} tokens with locked funds:\n`);

locked.forEach(l => {
console.log(`${l.token}:`);
console.log(` Locked: ${l.hold} (${l.percentLocked.toFixed(1)}%)`);
console.log(` Available: ${l.available}`);
});

5. Balance Summary Report#

Generate a comprehensive balance report:

async function getBalanceSummary(userAddress) {
const state = await getSpotClearinghouseState(userAddress);

console.log('\n=== Spot Balance Summary ===\n');

const nonZeroBalances = state.balances.filter(b => parseFloat(b.total) > 0);
const lockedBalances = state.balances.filter(b => parseFloat(b.hold) > 0);

console.log(`Total Tokens: ${nonZeroBalances.length}`);
console.log(`Tokens with Orders: ${lockedBalances.length}\n`);

console.log('Balances:');
nonZeroBalances.forEach(balance => {
const total = parseFloat(balance.total);
const hold = parseFloat(balance.hold);
const available = total - hold;

console.log(`\n${balance.coin}:`);
console.log(` Total: ${balance.total}`);
console.log(` Available: ${available.toFixed(8)}`);
if (hold > 0) {
console.log(` On Hold: ${balance.hold} ⚠️`);
}
});
}

Error Handling#

Common Errors#

ErrorCauseSolution
401 UnauthorizedInvalid API keyVerify your API key is correct
400 Bad RequestMissing or invalid user addressEnsure valid Ethereum address format
429 Too Many RequestsRate limit exceededImplement request throttling
500 Internal Server ErrorServer issueRetry with exponential backoff

Error Response Example#

{
"error": "Missing required parameter: user",
"code": "MISSING_PARAMETER"
}

Robust Error Handling#

async function safeGetSpotClearinghouseState(userAddress, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await getSpotClearinghouseState(userAddress);
} catch (error) {
if (error.response?.status === 429) {
// Rate limit - exponential backoff
await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
} else if (error.response?.status === 400) {
throw new Error('Invalid user address');
} else if (i === maxRetries - 1) {
throw error;
}
}
}
}

Best Practices#

  1. Poll responsibly — Don't poll more frequently than needed (every 5-10 seconds for active trading)
  2. Handle zero balances — Token may exist with zero balance
  3. Check available balance — Always calculate total - hold before placing orders
  4. Validate addresses — Ensure user addresses are valid Ethereum addresses
  5. Cache when appropriate — Cache balance data briefly to reduce API calls

Access real-time Hyperliquid spot balances with Dwellir's HyperCore Info Endpoint. Get your API key →