Consulta Basica
Copy
const balances = await pan.wallet.getBalances('pan_wallet_abc123');
console.log('Total USD:', balances.totalValueUsd);
console.log('Chains:', balances.chains);
Estructura de Respuesta
Copy
{
"walletId": "pan_wallet_abc123",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"chains": [
{
"chain": "ethereum",
"tokens": [
{
"asset": "USDC",
"balance": "200000000",
"balanceFormatted": "200.00",
"decimals": 6,
"valueUsd": 200.00
},
{
"asset": "ETH",
"balance": "500000000000000000",
"balanceFormatted": "0.50",
"decimals": 18,
"valueUsd": 1250.00
}
]
},
{
"chain": "arbitrum",
"tokens": [
{
"asset": "USDC",
"balance": "500000000",
"balanceFormatted": "500.00",
"decimals": 6,
"valueUsd": 500.00
}
]
},
{
"chain": "base",
"tokens": []
}
],
"totalValueUsd": 1950.00
}
Campos de Token
| Campo | Tipo | Descripcion |
|---|---|---|
asset | string | Simbolo del token (USDC, ETH) |
balance | string | Balance en unidades minimas |
balanceFormatted | string | Balance con decimales |
decimals | number | Decimales del token |
valueUsd | number | Valor en USD |
Formatear para UI
Copy
function formatearBalances(response) {
const items = [];
for (const chainData of response.chains) {
for (const token of chainData.tokens) {
// Ignorar balances cero
if (parseFloat(token.balanceFormatted) === 0) continue;
items.push({
chain: formatearNombreChain(chainData.chain),
chainId: chainData.chain,
token: token.asset,
amount: formatearNumero(token.balanceFormatted),
usd: formatearMoneda(token.valueUsd),
icon: getTokenIcon(token.asset),
explorerUrl: getExplorerUrl(chainData.chain, response.address)
});
}
}
// Ordenar por valor USD descendente
items.sort((a, b) => b.usd - a.usd);
return {
address: response.address,
items,
total: formatearMoneda(response.totalValueUsd)
};
}
function formatearNombreChain(chain) {
const nombres = {
'ethereum': 'Ethereum',
'ethereum-sepolia': 'Ethereum (Testnet)',
'arbitrum': 'Arbitrum',
'arbitrum-sepolia': 'Arbitrum (Testnet)',
'base': 'Base',
'base-sepolia': 'Base (Testnet)'
};
return nombres[chain] || chain;
}
function formatearNumero(valor) {
const num = parseFloat(valor);
if (num >= 1000000) return (num / 1000000).toFixed(2) + 'M';
if (num >= 1000) return (num / 1000).toFixed(2) + 'K';
if (num >= 1) return num.toFixed(2);
return num.toFixed(6);
}
function formatearMoneda(valor) {
return new Intl.NumberFormat('es-MX', {
style: 'currency',
currency: 'USD'
}).format(valor);
}
Calcular Total por Asset
Copy
function calcularTotalesPorAsset(chains) {
const totales = {};
for (const chainData of chains) {
for (const token of chainData.tokens) {
const asset = token.asset;
if (!totales[asset]) {
totales[asset] = {
asset,
totalBalance: 0,
totalUsd: 0,
chains: []
};
}
const balance = parseFloat(token.balanceFormatted);
totales[asset].totalBalance += balance;
totales[asset].totalUsd += token.valueUsd;
totales[asset].chains.push({
chain: chainData.chain,
balance,
usd: token.valueUsd
});
}
}
return Object.values(totales);
}
// Uso
const response = await pan.wallet.getBalances(walletId);
const porAsset = calcularTotalesPorAsset(response.chains);
console.log('USDC total:', porAsset.find(a => a.asset === 'USDC'));
// { asset: 'USDC', totalBalance: 1000, totalUsd: 1000, chains: [...] }
Verificar Fondos Suficientes
Copy
async function verificarFondos(walletId, asset, cantidadRequerida) {
const response = await pan.wallet.getBalances(walletId);
let disponible = 0;
const fuentes = [];
for (const chainData of response.chains) {
const token = chainData.tokens.find(t => t.asset === asset);
if (token) {
const balance = parseFloat(token.balanceFormatted);
disponible += balance;
if (balance > 0) {
fuentes.push({ chain: chainData.chain, balance });
}
}
}
return {
suficiente: disponible >= cantidadRequerida,
disponible,
requerido: cantidadRequerida,
faltante: Math.max(0, cantidadRequerida - disponible),
fuentes
};
}
// Uso
const check = await verificarFondos('pan_wallet_abc123', 'USDC', 1000);
if (!check.suficiente) {
console.log(`Faltan ${check.faltante} USDC`);
console.log('Deposita mas fondos para continuar');
} else {
console.log('Fondos suficientes');
console.log('Disponible en:', check.fuentes);
}
Componente React de Balances
Copy
import { useState, useEffect } from 'react';
function BalanceCard({ walletId }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const cargar = async () => {
try {
setLoading(true);
const response = await pan.wallet.getBalances(walletId);
setData(formatearBalances(response));
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
cargar();
}, [walletId]);
if (loading) return <div className="skeleton" />;
if (error) return <div className="error">{error}</div>;
if (!data) return null;
return (
<div className="balance-card">
<div className="header">
<h3>Balances</h3>
<button onClick={cargar} className="refresh">
Actualizar
</button>
</div>
<div className="address">
<span className="label">Direccion:</span>
<code>{data.address}</code>
<button onClick={() => navigator.clipboard.writeText(data.address)}>
Copiar
</button>
</div>
{data.items.length === 0 ? (
<div className="empty">
<p>Sin balances</p>
<p>Deposita fondos a tu direccion para comenzar</p>
</div>
) : (
<>
<div className="balances">
{data.items.map((item, i) => (
<div key={i} className="balance-row">
<div className="token">
<img src={item.icon} alt={item.token} />
<div>
<span className="symbol">{item.token}</span>
<span className="chain">{item.chain}</span>
</div>
</div>
<div className="amounts">
<span className="amount">{item.amount}</span>
<span className="usd">{item.usd}</span>
</div>
</div>
))}
</div>
<div className="total">
<span>Total</span>
<span className="total-usd">{data.total}</span>
</div>
</>
)}
<div className="updated">
Actualizado: {data.updatedAt}
</div>
</div>
);
}
Hook de Balances con Auto-refresh
Copy
import { useState, useEffect, useCallback } from 'react';
function useBalances(walletId, { autoRefresh = false, interval = 30000 } = {}) {
const [balances, setBalances] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetch = useCallback(async () => {
try {
const data = await pan.wallet.getBalances(walletId);
setBalances(data);
setError(null);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, [walletId]);
// Carga inicial
useEffect(() => {
fetch();
}, [fetch]);
// Auto-refresh
useEffect(() => {
if (!autoRefresh) return;
const timer = setInterval(fetch, interval);
return () => clearInterval(timer);
}, [autoRefresh, interval, fetch]);
// Helpers
const getTotal = (asset) => {
if (!balances) return 0;
let total = 0;
for (const chainData of balances.chains) {
const token = chainData.tokens.find(t => t.asset === asset);
if (token) total += parseFloat(token.balanceFormatted);
}
return total;
};
const hasFunds = (asset, amount) => {
return getTotal(asset) >= amount;
};
return {
balances,
loading,
error,
refresh: fetch,
getTotal,
hasFunds,
totalUsd: balances?.totalValueUsd || 0
};
}
// Uso
function WalletPage({ walletId }) {
const {
balances,
loading,
refresh,
getTotal,
hasFunds,
totalUsd
} = useBalances(walletId, { autoRefresh: true, interval: 60000 });
const usdcTotal = getTotal('USDC');
const puedePrestar = hasFunds('USDC', 100);
return (
<div>
<h2>Total: ${totalUsd.toFixed(2)}</h2>
<p>USDC: {usdcTotal}</p>
{puedePrestar && <button>Prestar</button>}
<button onClick={refresh}>Actualizar</button>
</div>
);
}
Polling Eficiente
Copy
class BalanceWatcher {
constructor(walletId, onChange) {
this.walletId = walletId;
this.onChange = onChange;
this.lastHash = null;
this.interval = null;
}
async check() {
const balances = await pan.wallet.getBalances(this.walletId);
// Crear hash simple de balances
const hash = JSON.stringify(balances.chains);
// Solo notificar si cambio
if (hash !== this.lastHash) {
this.lastHash = hash;
this.onChange(balances);
}
}
start(intervalMs = 30000) {
this.check(); // Inmediato
this.interval = setInterval(() => this.check(), intervalMs);
}
stop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
}
// Uso
const watcher = new BalanceWatcher('pan_wallet_abc123', (balances) => {
console.log('Balances actualizados:', balances.totalValueUsd);
actualizarUI(balances);
});
watcher.start(30000); // Cada 30 segundos
// Detener cuando no se necesite
watcher.stop();
