Skip to main content
Pan agrega tasas de rendimiento (APY) de protocolos DeFi en multiples chains, permitiendote encontrar las mejores oportunidades automaticamente.

Consulta Basica

const yields = await pan.yields.getAll();

console.log('Mejor APY:', yields.best);
console.log('Todos los yields:', yields.rates);

Estructura de Respuesta

{
  "rates": [
    {
      "chain": "base",
      "protocol": "aave",
      "asset": "USDC",
      "apy": 8.52
    },
    {
      "chain": "arbitrum",
      "protocol": "aave",
      "asset": "USDC",
      "apy": 7.23
    },
    {
      "chain": "ethereum",
      "protocol": "aave",
      "asset": "USDC",
      "apy": 5.15
    }
  ],
  "best": {
    "chain": "base",
    "asset": "USDC",
    "apy": 8.52,
    "reasoning": "Highest APY available"
  }
}

Campos de Rate

CampoTipoDescripcion
chainstringNombre de la chain
protocolstringProtocolo DeFi
assetstringToken soportado
apynumberAPY actual (porcentaje)

Mostrar Yields al Usuario

async function mostrarOportunidades() {
  const { rates, best } = await pan.yields.getAll();

  console.log('\n=== Mejores Oportunidades de Rendimiento ===\n');

  // Ordenar por APY descendente
  const ordenados = [...rates].sort((a, b) => b.apy - a.apy);

  ordenados.forEach((y, i) => {
    const mejor = y.chain === best.chain ? ' (MEJOR)' : '';
    console.log(`${i + 1}. ${y.chain} - ${y.protocol}`);
    console.log(`   APY: ${y.apy.toFixed(2)}%${mejor}`);
    console.log('');
  });
}

Decidir Donde Depositar

async function analizarMejorOpcion(walletId, amount) {
  // 1. Obtener balances y yields
  const [balancesResponse, yieldsResponse] = await Promise.all([
    pan.wallet.getBalances(walletId),
    pan.yields.getAll()
  ]);

  const { rates, best } = yieldsResponse;

  // 2. Encontrar donde estan los fondos
  const fuentes = [];
  for (const chainData of balancesResponse.chains) {
    const usdc = chainData.tokens.find(t => t.asset === 'USDC');
    if (usdc && parseFloat(usdc.balanceFormatted) > 0) {
      fuentes.push({
        chain: chainData.chain,
        balance: parseFloat(usdc.balanceFormatted)
      });
    }
  }

  // 3. Calcular escenarios
  console.log('\n=== Analisis de Opciones ===\n');

  // Opcion A: Depositar donde estan los fondos (sin bridge)
  for (const fuente of fuentes) {
    const yieldLocal = rates.find(y => y.chain === fuente.chain);
    if (yieldLocal) {
      const gananciaAnual = fuente.balance * (yieldLocal.apy / 100);
      console.log(`Sin bridge (${fuente.chain}):`);
      console.log(`  APY: ${yieldLocal.apy}%`);
      console.log(`  Ganancia anual estimada: $${gananciaAnual.toFixed(2)}`);
      console.log('');
    }
  }

  // Opcion B: Bridge al mejor APY
  const gananciaOptima = amount * (best.apy / 100);
  console.log(`Con bridge a ${best.chain}:`);
  console.log(`  APY: ${best.apy}%`);
  console.log(`  Ganancia anual estimada: $${gananciaOptima.toFixed(2)}`);

  // 4. Calcular si vale la pena el bridge
  const apyLocal = rates.find(y => fuentes.some(f => f.chain === y.chain))?.apy || 0;
  const diferenciaApy = best.apy - apyLocal;
  const gananciaExtraAnual = amount * (diferenciaApy / 100);

  // Asumir costo de bridge ~$3
  const costoBridge = 3;
  const diasParaRecuperar = (costoBridge / gananciaExtraAnual) * 365;

  console.log(`\nAnalisis:`);
  console.log(`  Diferencia APY: ${diferenciaApy.toFixed(2)}%`);
  console.log(`  Ganancia extra anual: $${gananciaExtraAnual.toFixed(2)}`);
  console.log(`  Dias para recuperar costo de bridge: ${diasParaRecuperar.toFixed(0)}`);

  if (diasParaRecuperar < 30) {
    console.log(`  Recomendacion: Bridge a ${best.chain}`);
    return { action: 'bridge', target: best.chain };
  } else {
    console.log(`  Recomendacion: Depositar localmente`);
    return { action: 'local', target: fuentes[0]?.chain };
  }
}

Componente React de Yields

import { useState, useEffect } from 'react';

function YieldsTable() {
  const [yields, setYields] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function cargar() {
      const data = await pan.yields.getAll();
      setYields(data);
      setLoading(false);
    }
    cargar();
  }, []);

  if (loading) return <div>Cargando yields...</div>;

  return (
    <div className="yields-table">
      <h3>Oportunidades de Rendimiento</h3>

      <div className="best-yield">
        <span>Mejor APY:</span>
        <span className="apy">{yields.best.apy.toFixed(2)}%</span>
        <span className="chain">en {yields.best.chain}</span>
      </div>

      <table>
        <thead>
          <tr>
            <th>Chain</th>
            <th>Protocolo</th>
            <th>APY</th>
          </tr>
        </thead>
        <tbody>
          {yields.rates
            .sort((a, b) => b.apy - a.apy)
            .map((y) => (
              <tr
                key={`${y.chain}-${y.protocol}`}
                className={y.chain === yields.best.chain ? 'best' : ''}
              >
                <td>
                  <ChainIcon chain={y.chain} />
                  {y.chain}
                </td>
                <td>
                  <ProtocolIcon protocol={y.protocol} />
                  {y.protocol}
                </td>
                <td className="apy">
                  {y.apy.toFixed(2)}%
                </td>
              </tr>
            ))}
        </tbody>
      </table>
    </div>
  );
}

function RiskBadge({ level }) {
  const colors = {
    low: 'green',
    medium: 'yellow',
    high: 'red'
  };

  const labels = {
    low: 'Bajo',
    medium: 'Medio',
    high: 'Alto'
  };

  return (
    <span className={`risk-badge ${colors[level]}`}>
      {labels[level]}
    </span>
  );
}

Calcular Ganancias Estimadas

function calcularGanancias(monto, apy, periodos = [30, 90, 365]) {
  const tasaDiaria = apy / 365 / 100;

  return periodos.map(dias => {
    // Interes compuesto diario
    const montoFinal = monto * Math.pow(1 + tasaDiaria, dias);
    const ganancia = montoFinal - monto;

    return {
      dias,
      periodo: dias === 30 ? '1 mes' : dias === 90 ? '3 meses' : '1 año',
      montoFinal: montoFinal.toFixed(2),
      ganancia: ganancia.toFixed(2),
      porcentaje: ((ganancia / monto) * 100).toFixed(2)
    };
  });
}

// Uso
const ganancias = calcularGanancias(1000, 8.52);
console.log(ganancias);
// [
//   { dias: 30, periodo: '1 mes', montoFinal: '1006.97', ganancia: '6.97', porcentaje: '0.70' },
//   { dias: 90, periodo: '3 meses', montoFinal: '1021.12', ganancia: '21.12', porcentaje: '2.11' },
//   { dias: 365, periodo: '1 año', montoFinal: '1088.97', ganancia: '88.97', porcentaje: '8.90' }
// ]

Alertas de APY

class YieldMonitor {
  constructor(minApy) {
    this.minApy = minApy;
    this.lastBestApy = null;
  }

  async check() {
    const { best } = await pan.yields.getAll();

    const alerts = [];

    // Alerta si APY supera umbral
    if (best.apy >= this.minApy) {
      alerts.push({
        type: 'high_apy',
        message: `APY de ${best.apy}% disponible en ${best.chain}`,
        yield: best
      });
    }

    // Alerta si APY cambio significativamente
    if (this.lastBestApy) {
      const cambio = best.apy - this.lastBestApy;
      if (Math.abs(cambio) > 1) {
        alerts.push({
          type: 'apy_change',
          message: `APY ${cambio > 0 ? 'subio' : 'bajo'} ${Math.abs(cambio).toFixed(2)}%`,
          from: this.lastBestApy,
          to: best.apy
        });
      }
    }

    this.lastBestApy = best.apy;
    return alerts;
  }

  async startMonitoring(intervalMs = 300000, onAlert) {
    // Check inicial
    const alerts = await this.check();
    if (alerts.length) onAlert(alerts);

    // Monitoreo periodico
    setInterval(async () => {
      const alerts = await this.check();
      if (alerts.length) onAlert(alerts);
    }, intervalMs);
  }
}

// Uso
const monitor = new YieldMonitor(8.0);
monitor.startMonitoring(300000, (alerts) => {
  alerts.forEach(alert => {
    console.log(`[ALERTA] ${alert.message}`);
    // Enviar notificacion, email, etc.
  });
});

Proximos Pasos