Los secretos de WayBank ¿Como Funciona?

WayBank, aprovechando la tecnología blockchain, emplea protocolos de liquidez automatizada para permitir intercambios de USDT y USDC dentro de la red Polygon de manera completamente descentralizada. Estos protocolos se basan en un conjunto de Smart Contracts diseñados por Hayden Adams, inspirados en ideas de Vitalik Buterin.

El funcionamiento de WayBank se centra en los Smart Contracts, que no solo operan en la red Polygon sino que también actúan como pools de intercambio. Por ejemplo, un pool entre USDT y USDC constituye un Smart Contract específico, alimentado por fondos de proveedores de liquidez (LP). En este contexto, un proveedor como el Usuario A (LP) suministra ambas criptomonedas en proporciones que reflejan su tasa de cambio actual.

A cambio de su aporte, el Usuario A (LP) recibe tokens en Uniswap (GAS: JBHC), simbolizando su participación en el pool y el interés acumulado, similar al funcionamiento de los pools en plataformas como Compound. Estos intereses provienen de las comisiones generadas por operaciones Swap realizadas por otros usuarios.

Además, WayBank utiliza dos tipos principales de Smart Contracts: Exchange Contracts y Factory Contracts. Los Exchange Contracts gestionan cada par de intercambio y albergan los fondos de los proveedores de liquidez, permitiendo así los intercambios directos sin intermediarios. Por otro lado, los Factory Contracts facilitan la creación de nuevos Exchange Contracts y juegan un papel crucial en el registro de tokens como JBHC.

El modelo «Constant Product Market Maker» (CPMM) es vital para asegurar la liquidez en todos los intercambios. Este modelo utiliza una fórmula matemática, 𝑋 * 𝑌 = 𝐾, donde 𝑋 y 𝑌 representan las cantidades de diferentes tokens en el pool, y 𝐾 es una constante. Esta relación garantiza que el pool nunca se quede sin fondos, ajustando la tasa de intercambio según la liquidez disponible, lo que es crucial especialmente en operaciones grandes que podrían alterar significativamente la balanza del pool.

Con este innovador enfoque, WayBank no solo facilita transacciones seguras y eficientes entre USDT y USDC, sino que también protege la inversión de los usuarios, asegurando una liquidez constante y una respuesta adaptable a las condiciones del mercado.

Para ello se utiliza UNISWAP y recientemente también QUIKSWAP para estas transacciones de WayBank. Plataformas que están operando desde 2018 para crear liquidez e intercambiar tokens. Es uno de los dos primeros exchanges descentralizados a nivel mundial. Esto significa que no tiene intermediarios y cualquiera puede operar de forma rápida y eficiente.

¿Qué es UniSwap?

Uniswap es una plataforma de intercambio descentralizada (DEX) que opera sobre la blockchain de Ethereum. Es fundamental en el ecosistema de las finanzas descentralizadas (DeFi) y permite a los usuarios intercambiar criptomonedas sin necesidad de un intermediario centralizado. Utiliza un modelo específico llamado «Automated Market Maker» (AMM) para facilitar el comercio y proporcionar liquidez.

En Uniswap, no hay libros de órdenes tradicionales ni intermediarios. En su lugar, los fondos se agregan a pools de liquidez por los propios usuarios, quienes depositan pares de tokens en contratos inteligentes. Cualquier par de tokens ERC-20 puede tener su propio pool, y los precios dentro de estos pools se determinan mediante una fórmula matemática que equilibra el valor de los tokens en el pool, manteniendo el producto de sus cantidades constante (la fórmula 𝑋×𝑌=𝐾).

Los proveedores de liquidez que depositan sus tokens en estos pools reciben tokens de proveedor de liquidez, también conocidos como LP tokens, que representan su parte del pool y les permiten reclamar una porción de las comisiones generadas por las operaciones de intercambio en ese pool. Esto incentiva a los usuarios a suministrar liquidez al sistema.

Uniswap es notable por su interfaz de usuario sencilla, su capacidad de facilitar un intercambio rápido y directo entre dos tokens, y por promover un sistema financiero más accesible y resistente a la censura.

 

¿Qué es la minería de liquidez?

La minería de liquidez, también conocida como cultivo de rendimiento (yield farming), es el acto de proporcionar liquidez a través de criptomonedas a intercambios descentralizados (DEX). Dado que el objetivo principal de un intercambio es ser líquido, los DEX buscan recompensar a los usuarios que estén dispuestos a aportar capital a su plataforma.

La mayoría de los DEX están descentralizados reemplazando los libros de pedidos con un Creador de Mercado Automático (AMM). Un AMM es un contrato inteligente (SmartContract) que regula el comercio. Dado que los contratos inteligentes están descentralizados, los usuarios no tienen que negociar la cartera de pedidos de un intercambio. En cambio, comercian efectivamente con otros usuarios.

La AMM recauda estas tarifas y las otorga a cada proveedor de liquidez (LP) como recompensa . Por tanto, el DEX ofrece un ecosistema simbiótico donde cada grupo de usuarios se ayuda entre sí. Mientras que el intercambiador de tokens paga una pequeña tarifa para operar en un intercambio descentralizado, el proveedor de liquidez gana dinero por proporcionar la liquidez que necesitará el primer usuario.

Structura de un SmartContract en WayBank

Informe Pericial sobre el Smart Contract “WayBank” y su Funcionamiento con Pools de Uniswap V3


Informe Pericial sobre el Contrato “WayBank” (Versión Completa y Operativa)


1. Introducción

El contrato “WayBank” es una implementación de un protocolo DeFi que permite:

  1. Depositar USDC y recibir a cambio un token ERC-20 (“WAY”) como recibo de la participación.
  2. Interactuar con Uniswap V3 para crear y gestionar una posición de liquidez.
  3. Valorar de forma aproximada un segundo token (otroToken) frente a USDC mediante un oráculo de precios (Chainlink).
  4. Calcular y repartir beneficios usando la funcionalidad de Snapshots (ERC20Snapshot), lo que permite un reparto más escalable y flexible.
  5. Aplicar una comisión fija del 35% a los beneficios (en favor de WALLET_FEE).
  6. Incluir un mecanismo de reequilibrio automático mediante Keeper (opcional).
  7. Establecer un sistema de liquidación final (“liquidarContrato”) que transfiere fondos a WALLET_OWNER y pausa el contrato.
  8. Contar con un sistema de pausas y reentrancy guard (protección frente a reentradas) para mayor seguridad.

A continuación, se expondrá un análisis de su estructura, funcionamiento y consideraciones de seguridad.


2. Estructura del Contrato

2.1. Herencia y bibliotecas

  • ERC20Snapshot: permite la creación de “fotografías” (snapshots) de los balances. Se usa para repartir beneficios sin iterar sobre todos los holders en una sola transacción.
  • Pausable y ReentrancyGuard:
    • Pausable protege funciones marcadas con whenNotPaused; puede pausarse o reanudarse la operativa.
    • ReentrancyGuard evita ataques de reentrada.
  • Ownable: otorga al owner permisos administrativos (ej. crear posición en Uniswap, liquidar contrato, etc.).
  • SafeERC20: Uso de funciones seguras para transferencias y aprobaciones de tokens ERC20, minimizando riesgos.

2.2. Variables de configuración

  1. Ventana de depósitos: fechaInicio y fechaFin restringen en qué momento se pueden realizar depósitos.
  2. Tokens gestionados:
    • usdcToken: Principal token de depósito (USDC).
    • otroToken: Un segundo token que puede formar parte de la liquidez en Uniswap V3 (ej. WETH).
  3. Uniswap V3:
    • positionManager: Interface principal para crear y manejar posiciones NFT.
    • feeTier: El nivel de comisiones de la pool (500, 3000 o 10000).
    • uniswapPositionTokenId: ID del NFT que representa la posición de liquidez en Uniswap.
  4. Oráculo de precios: priceFeed (AggregatorV3Interface) para valorizar otroToken en USDC.
  5. Beneficios:
    • FEE_PERCENTAGE: 35% fijo para comisiones.
    • periodicidadLiquidacion: tiempo mínimo entre liquidaciones.
    • ultimaLiquidacion y ultimaValoracion: para calcular los beneficios en base a la valorización.
  6. Direcciones:
    • WALLET_FEE: dirección que recibe las comisiones (35%).
    • WALLET_OWNER: dirección a la que se transfieren todos los fondos al finalizar (liquidarContrato).

3. Funcionalidades Principales

3.1. Depósito y emisión de tokens “WAY”

  • Función: depositar(uint256 cantidad)
    • Solo se permite si block.timestamp está entre fechaInicio y fechaFin.
    • El usuario transfiere “cantidad” de USDC al contrato.
    • Se hace un mint de tokens WAY al usuario en una tasa 1:1.

3.2. Retiro y quema de tokens WAY

  • Función: retirar(uint256 cantidad)
    • Exige que el usuario tenga ≥ “cantidad” de WAY.
    • Se queman los WAY y se transfiere la misma cantidad de USDC.
    • Requiere que el contrato tenga suficiente liquidez en USDC.

3.3. Creación y gestión de la posición en Uniswap V3

  1. Crear posición: crearPosicionUniswap(…)
    • Aprobaciones de USDC y otroToken para positionManager.
    • Llamada a positionManager.mint(params) con los parámetros de tickLower, tickUpper, etc.
    • Guarda en uniswapPositionTokenId el ID del NFT resultante.
  2. Recolectar comisiones: recolectarFeesUniswap()
    • Usa positionManager.collect(params) para reunir las fees generadas por la liquidez.
  3. Retirar liquidez: retirarLiquidezUniswap(liquidity)
    • Llama a positionManager.decreaseLiquidity(params) con la cantidad de liquidez a liberar.
    • A continuación, llama a recolectarFeesUniswap() para depositar los tokens en el contrato.

3.4. Oráculo de precios Chainlink

  • El contrato provee getLatestPrice() para obtener la cotización de otroToken/USD.
  • convertirOtroTokenAUSDC(uint256 balanceOtro) multiplica el balance de “otroToken” por el precio actual para estimar su valor en USDC (asumiendo 8 decimales en el feed).

3.5. Cálculo de beneficios y reparto escalable

  1. Valoración total: valoracionTotal()
    • Suma el balance de USDC en el contrato + la conversión del saldo de otroToken a USDC (oráculo).
    • No incluye el valor no reclamado en Uniswap a menos que se recolecte.
  2. Liquidar beneficios: liquidarBeneficios()
    • Verifica que haya transcurrido periodicidadLiquidacion desde la última.
    • Llama a recolectarFeesUniswap() y recalcula valoracionTotal().
    • Si la nueva valoración supera ultimaValoracion, la diferencia son los beneficios.
    • Cobra 35% (FEE_PERCENTAGE) como comisión.
    • El 65% restante se define como “beneficios netos”.
    • Hace un _snapshot() para permitir repartos con claimBeneficios.
    • Actualiza ultimaValoracion y ultimaLiquidacion.
  3. Claim de beneficios: claimBeneficios(snapshotId)
    • Permite a cada usuario retirar su parte proporcional de los beneficios netos registrados en beneficiosPorSnapshot[snapshotId].
    • Evita bucles masivos en una sola transacción (patrón de “pull payment”).

3.6. Reequilibrio automático (Keeper)

  • Funciones: checkUpkeep y performUpkeep
    • Estructura genérica para integrarse con Chainlink Automation o Gelato.
    • Queda a implementar la lógica de “ver si la posición está fuera de rango” y reequilibrar en performUpkeep.

3.7. Liquidación final del contrato

  • Función: liquidarContrato()
    • Requiere block.timestamp > fechaFin.
    • Retira toda la liquidez de Uniswap, si existe.
    • Transfiere el saldo de USDC y otroToken al WALLET_OWNER.
    • Pausa el contrato, impidiendo más operaciones.

4. Consideraciones de Seguridad y Riesgos

  1. Dependencia en un owner:
    • Todas las funciones críticas (crearPosicionUniswap, retirarLiquidezUniswap, liquidarContrato) están restringidas a onlyOwner.
    • Se recomienda que owner sea un multisig para minimizar riesgos de un solo firmante.
  2. Oráculo Chainlink:
    • Se asume que el feed “otroToken / USD” es confiable y estable. Debe revisarse que el feed sea válido y seguro.
  3. Impermanent Loss:
    • Al depositar en Uniswap V3, los activos están sujetos a la volatilidad del par. El valor real podría diferir de la simple conversión oracular, especialmente si no se retira la liquidez antes de calcular la valoración.
  4. Escalabilidad del Reparto:
    • El uso de ERC20Snapshot y funciones de reclamo individuales (claimBeneficios) es apropiado para un número amplio de depositantes.
  5. Ilustración de Reequilibrio:
    • Las funciones checkUpkeep y performUpkeep sirven de ejemplo. La lógica concreta (cálculo de ticks, precio, etc.) debe implementarse si se desea automatizar el mantenimiento de la posición.
  6. Comisión fija:
    • Se descuenta el 35% de los beneficios calculados. Este valor es inmutable en el código (FEE_PERCENTAGE = 35).
  7. Pausable y ReentrancyGuard:
    • Adecuada protección ante llamadas sucesivas. Permite congelar el contrato en caso de emergencia (pausar() y despausar()).

5. Aspectos Destacables

  • Uso de _snapshot() en liquidarBeneficios para repartir utilidades.
  • Conversión de “otroToken” a USDC mediante oráculo, incrementando la precisión en la medición de beneficios.
  • Función de “liquidarContrato” retira totalmente la posición y transfiere los fondos, simplificando el cierre del ciclo.
  • Modelo 1:1 de emisión de WAY respecto a USDC depositado, facilitando la contabilidad de cada usuario.

6. Recomendaciones

  1. Auditoría externa:
    • Contratar una firma especializada para examinar la seguridad y la lógica financiera, dada la complejidad (Uniswap V3, oráculos, comisiones, etc.).
  2. Configurar oráculo apropiado:
    • Asegurarse de usar el feed correcto para otroToken/USD y tener en cuenta los decimales.
  3. Revisión de “owner” y gobernanza:
    • Migrar owner a un multisig o introducir un módulo de gobernanza si se desea mayor descentralización.
  4. Monitoreo de precios y reequilibrio:
    • Completar la lógica de checkUpkeep y performUpkeep para ajustar la posición cuando el rango de Uniswap sea desfavorable.
  5. Documentación clara:
    • Indicar a los usuarios los riesgos de impermanent loss y el mecanismo de comisiones y reparto.

7. Conclusión

El contrato “WayBank” ofrece una solución integral para manejar depósitos en USDC, invertir en Uniswap V3, valuar activos mediante Chainlink y repartir rendimientos usando instantáneas de balances (snapshots). La inclusión de medidas de seguridad (reentrancy, pausas, propiedad controlada) y la posibilidad de automatizar reequilibrios lo hace un diseño robusto.

No obstante, se deben considerar las recomendaciones apuntadas (auditorías, oráculos, gobernanza) para una implementación productiva que brinde seguridad y transparencia a los participantes. Con dichas acciones, “WayBank” puede ser un sólido protocolo DeFi que integra varias funcionalidades avanzadas en un solo contrato.


8. Código fuente

				
					// SPDX-License-Identifier: GPL-3.0
// WayBank - Contrato Completo y Operativo

pragma solidity ^0.8.17;

// ---------------------------
// IMPORTS
// ---------------------------
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol"; 
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";

// Interfaz Oráculo Chainlink (para valorar 'otroToken' vs USD o vs USDC)
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

// Interfaz Keeper (Chainlink Automation / Gelato) para reequilibrio automático (opcional)
interface IKeeperCompatible {
    function checkUpkeep(bytes calldata checkData) external returns (bool upkeepNeeded, bytes memory performData);
    function performUpkeep(bytes calldata performData) external;
}

/**
 * @title WayBank
 * @dev Contrato final que integra:
 *  - Depósitos en USDC y emisión de tokens WAY (1:1).
 *  - Posición en Uniswap V3 (crear, recolectar fees, retirar liquidez).
 *  - Oráculo de precios (Chainlink) para valorar 'otroToken' en USDC.
 *  - Cálculo de beneficios con ERC20Snapshot para reparto escalable.
 *  - Comisión fija del 35% hacia WALLET_FEE.
 *  - Pausa en caso de emergencia, y liquidación final tras 'fechaFin'.
 *  - Ejemplo de funciones para reequilibrio automático (checkUpkeep/performUpkeep).
 */
contract WayBank is ERC20Snapshot, Pausable, ReentrancyGuard, Ownable {
    using SafeERC20 for IERC20;

    // ------------------------------------------------
    // 1) CONFIGURACIÓN PRINCIPAL
    // ------------------------------------------------

    // Ventana de depósitos
    uint256 public fechaInicio;
    uint256 public fechaFin;

    // Periodo mínimo entre liquidaciones
    uint256 public periodicidadLiquidacion;

    // Token de depósito principal (ej. USDC)
    IERC20 public usdcToken;

    // Otro token (ej. WETH) para la posición en Uniswap
    IERC20 public otroToken;

    // Parámetro fijo de comisión para liquidarBeneficios
    uint256 public constant FEE_PERCENTAGE = 35;

    // Dirección que recibirá las comisiones
    address public constant WALLET_FEE = 0xbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;

    // Dirección del propietario de los fondos al finalizar el contrato
    address public immutable WALLET_OWNER = 0xbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;

    // ---------------------------
    // 2) UNISWAP V3
    // ---------------------------
    INonfungiblePositionManager public positionManager;  // Interfaz para crear/gestionar posición
    uint24 public immutable feeTier;                     // Fee de la pool Uniswap (500, 3000, 10000)
    uint256 public uniswapPositionTokenId;               // tokenId de la posición en Uniswap

    // Dirección de la pool (opcional) si deseas invocar funciones directas
    address public constant POOL = 0xbXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;

    // ---------------------------
    // 3) ORÁCULO DE PRECIOS
    // ---------------------------
    AggregatorV3Interface public priceFeed; // p.ej., 'otroToken' vs USD (o USDC)

    // ---------------------------
    // 4) CONTABILIDAD DE BENEFICIOS
    // ---------------------------
    uint256 public ultimaLiquidacion;
    uint256 public ultimaValoracion; 
    uint256 public porcentajeRendimiento; // Si deseas un % teórico adicional

    // ---------------------------
    // EVENTOS
    // ---------------------------
    event Depositado(address indexed usuario, uint256 cantidadUSDC);
    event Retirado(address indexed usuario, uint256 cantidadUSDC);
    event LiquidezAnadida(uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
    event LiquidezRetirada(uint256 tokenId, uint256 amount0, uint256 amount1);
    event FeesRecolectados(uint256 tokenId, uint256 fees0, uint256 fees1);

    event BeneficiosLiquidados(uint256 beneficiosTotales, uint256 timestamp);
    event BeneficiosDisponibles(uint256 snapshotId, uint256 beneficiosNetos);

    event ContratoLiquidado(uint256 saldoUSDC, uint256 saldoOtroToken);
    event ReequilibrioAutomatizado(uint256 timestamp);

    // ------------------------------------------------
    // MAPPINGS para REPARTO DE BENEFICIOS via SNAPSHOT
    // ------------------------------------------------
    mapping(uint256 => uint256) public beneficiosPorSnapshot; // snapshotId => totalBeneficiosNetos
    mapping(address => mapping(uint256 => bool)) public yaReclamado; // user => snapshotId => si reclamó

    // ------------------------------------------------
    // CONSTRUCTOR
    // ------------------------------------------------
    constructor(
        uint256 _fechaInicio,
        uint256 _fechaFin,
        uint256 _periodicidadLiquidacion,
        uint256 _porcentajeRendimiento,
        address _usdcAddress,
        address _otroTokenAddress,
        address _positionManager,
        address _walletOwner,
        uint24 _feeTier,
        address _chainlinkPriceFeed
    ) 
        ERC20("WayBank Token", "WAY")
    {
        // Validación de feeTier
        require(_feeTier == 500 || _feeTier == 3000 || _feeTier == 10000, "Fee tier no valido");

        // Configuración inicial
        fechaInicio = _fechaInicio;
        fechaFin = _fechaFin;
        periodicidadLiquidacion = _periodicidadLiquidacion;
        porcentajeRendimiento = _porcentajeRendimiento;

        usdcToken = IERC20(_usdcAddress);
        otroToken = IERC20(_otroTokenAddress);
        positionManager = INonfungiblePositionManager(_positionManager);

        WALLET_OWNER = _walletOwner;
        feeTier = _feeTier;

        priceFeed = AggregatorV3Interface(_chainlinkPriceFeed);

        // Estado inicial
        ultimaLiquidacion = block.timestamp;
        ultimaValoracion = 0;
        uniswapPositionTokenId = 0;

        // Transferir propiedad (se recomienda que _walletOwner sea un multisig)
        transferOwnership(msg.sender);
    }

    // ------------------------------------------------
    // MODIFICADORES
    // ------------------------------------------------
    modifier soloDuranteDepositos() {
        require(block.timestamp >= fechaInicio, "Ventana de depositos no iniciada");
        require(block.timestamp <= fechaFin, "Ventana de depositos finalizada");
        _;
    }

    // ------------------------------------------------
    // DEPÓSITO / RETIRO DE USDC
    // ------------------------------------------------
    function depositar(uint256 cantidad) external whenNotPaused nonReentrant soloDuranteDepositos {
        require(cantidad > 0, "Cantidad debe ser > 0");
        
        // Transferir USDC desde usuario al contrato
        usdcToken.safeTransferFrom(msg.sender, address(this), cantidad);

        // Emitir tokens WAY 1:1
        _mint(msg.sender, cantidad);
        emit Depositado(msg.sender, cantidad);
    }

    function retirar(uint256 cantidad) external whenNotPaused nonReentrant {
        require(cantidad > 0, "Debe retirar > 0");
        require(balanceOf(msg.sender) >= cantidad, "Saldo WAY insuficiente");

        // Quemar tokens WAY
        _burn(msg.sender, cantidad);

        // Transferir USDC al usuario (el contrato debe tener liquidez)
        uint256 saldoContrato = usdcToken.balanceOf(address(this));
        require(saldoContrato >= cantidad, "Liquidez insuficiente en el contrato");

        usdcToken.safeTransfer(msg.sender, cantidad);
        emit Retirado(msg.sender, cantidad);
    }

    // ------------------------------------------------
    // UNISWAP: CREAR POSICIÓN, RETIRAR LIQUIDEZ, RECOLECTAR FEES
    // ------------------------------------------------
    function crearPosicionUniswap(
        uint256 amount0Desired,
        uint256 amount1Desired,
        int24 tickLower,
        int24 tickUpper
    ) external onlyOwner whenNotPaused {
        // Solo se permite una posición en este ejemplo
        require(uniswapPositionTokenId == 0, "Ya existe una posicion en Uniswap");

        // Aprobamos el gasto
        usdcToken.safeApprove(address(positionManager), 0);
        usdcToken.safeApprove(address(positionManager), amount0Desired);

        otroToken.safeApprove(address(positionManager), 0);
        otroToken.safeApprove(address(positionManager), amount1Desired);

        // Mint de la posición
        INonfungiblePositionManager.MintParams memory params =
            INonfungiblePositionManager.MintParams({
                token0: address(usdcToken),
                token1: address(otroToken),
                fee: feeTier,
                tickLower: tickLower,
                tickUpper: tickUpper,
                amount0Desired: amount0Desired,
                amount1Desired: amount1Desired,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: block.timestamp + 1800
            });

        (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) =
            positionManager.mint(params);

        // Guardar tokenId
        uniswapPositionTokenId = tokenId;

        // Limpiar aprobaciones sobrantes
        if (amount0 < amount0Desired) {
            usdcToken.safeApprove(address(positionManager), 0);
        }
        if (amount1 < amount1Desired) {
            otroToken.safeApprove(address(positionManager), 0);
        }

        emit LiquidezAnadida(tokenId, liquidity, amount0, amount1);
    }

    function recolectarFeesUniswap() public onlyOwner whenNotPaused {
        require(uniswapPositionTokenId != 0, "No existe posicion Uniswap");

        INonfungiblePositionManager.CollectParams memory params =
            INonfungiblePositionManager.CollectParams({
                tokenId: uniswapPositionTokenId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            });

        (uint256 amount0Collected, uint256 amount1Collected) =
            positionManager.collect(params);

        emit FeesRecolectados(uniswapPositionTokenId, amount0Collected, amount1Collected);
    }

    function retirarLiquidezUniswap(uint128 liquidity) external onlyOwner whenNotPaused {
        require(uniswapPositionTokenId != 0, "No existe posicion Uniswap");
        require(liquidity > 0, "Liquidez debe ser > 0");

        INonfungiblePositionManager.DecreaseLiquidityParams memory params =
            INonfungiblePositionManager.DecreaseLiquidityParams({
                tokenId: uniswapPositionTokenId,
                liquidity: liquidity,
                amount0Min: 0,
                amount1Min: 0,
                deadline: block.timestamp + 1800
            });

        (uint256 amount0, uint256 amount1) = positionManager.decreaseLiquidity(params);
        
        // Recolectar fees, incluyendo lo recién liberado
        recolectarFeesUniswap();

        emit LiquidezRetirada(uniswapPositionTokenId, amount0, amount1);
    }

    // ------------------------------------------------
    // ORÁCULO CHAINLINK: VALORACIÓN DE 'otroToken' EN USDC
    // ------------------------------------------------
    function getLatestPrice() public view returns (int256) {
        // Devuelve precio "otroToken / USD", asumiendo
        (
            , 
            int256 price, 
            , 
            , 
        ) = priceFeed.latestRoundData();
        return price;
    }

    function convertirOtroTokenAUSDC(uint256 balanceOtro) public view returns (uint256) {
        if (balanceOtro == 0) return 0;
        int256 price = getLatestPrice();
        // Asumimos 8 decimales en el feed. Ajustar si difiere.
        uint256 priceUint = uint256(price);
        // (balanceOtro * price) / 10^8
        uint256 valorUSDC = (balanceOtro * priceUint) / (10 ** 8);
        return valorUSDC;
    }

    // ------------------------------------------------
    // VALORACIÓN TOTAL / LIQUIDACIÓN DE BENEFICIOS
    // ------------------------------------------------
    function valoracionTotal() public view returns (uint256) {
        // 1) USDC en el contrato
        uint256 balanceUSDC = usdcToken.balanceOf(address(this));
        // 2) 'otroToken' convertido a USDC (aprox. via oráculo)
        uint256 balanceOT = otroToken.balanceOf(address(this));
        uint256 balanceOTenUSDC = convertirOtroTokenAUSDC(balanceOT);
        // 3) Valor en la posición Uniswap si no se ha retirado => en este ejemplo
        //    no se contabiliza a menos que se recolecte. Podrías ampliar
        //    usando las librerías de Uniswap para calcularlo on-chain.
        return balanceUSDC + balanceOTenUSDC;
    }

    function liquidarBeneficios() external whenNotPaused nonReentrant {
        // Respetar periodicidad
        require(block.timestamp >= (ultimaLiquidacion + periodicidadLiquidacion), "No corresponde liquidar aun");

        // Recolectar fees de Uniswap para actualizar balance
        if (uniswapPositionTokenId != 0) {
            recolectarFeesUniswap();
        }

        // Cálculo de beneficios basado en la valoración actual - ultimaValoracion
        uint256 valorActual = valoracionTotal();
        if (valorActual <= ultimaValoracion) {
            // No hay ganancia
            ultimaLiquidacion = block.timestamp;
            emit BeneficiosLiquidados(0, block.timestamp);
            return;
        }

        uint256 beneficios = valorActual - ultimaValoracion;

        // Comisión
        uint256 comision = (beneficios * FEE_PERCENTAGE) / 100;
        if (comision > 0) {
            usdcToken.safeTransfer(WALLET_FEE, comision);
        }
        uint256 beneficiosNetos = beneficios - comision;

        // Actualizar
        ultimaValoracion = valorActual;
        ultimaLiquidacion = block.timestamp;

        // Crear snapshot para permitir "pull payments" en claimBeneficios
        uint256 snapshotId = _snapshot();

        emit BeneficiosLiquidados(beneficios, block.timestamp);
        emit BeneficiosDisponibles(snapshotId, beneficiosNetos);

        // (Opcional) Guardar la cantidad total de beneficios netos en el snapshot
        beneficiosPorSnapshot[snapshotId] = beneficiosNetos;
    }

    /**
     * @dev Lógica para que cada usuario reclame su parte de los beneficios
     *      almacenados en `beneficiosPorSnapshot[snapshotId]`.
     */
    function claimBeneficios(uint256 snapshotId) external whenNotPaused nonReentrant {
        require(!yaReclamado[msg.sender][snapshotId], "Beneficios ya reclamados en este snapshot");
        uint256 totalBenef = beneficiosPorSnapshot[snapshotId];
        require(totalBenef > 0, "No hay beneficios en ese snapshot");

        // Balance del usuario y totalSupply en ese snapshot
        (uint256 balanceUser, uint256 supplySnapshot) = _balanceOfAt(msg.sender, snapshotId);
        require(balanceUser > 0 && supplySnapshot > 0, "No participaciones en snapshot");

        // Cálculo de la parte
        uint256 parte = (totalBenef * balanceUser) / supplySnapshot;
        require(parte > 0, "Parte = 0");

        // Marcar reclamado
        yaReclamado[msg.sender][snapshotId] = true;

        // Transferir al usuario
        usdcToken.safeTransfer(msg.sender, parte);
    }

    // Función interna para obtener balances de snapshot
    function _balanceOfAt(address account, uint256 snapshotId) internal view returns (uint256, uint256) {
        return (balanceOfAt(account, snapshotId), totalSupplyAt(snapshotId));
    }

    // ------------------------------------------------
    // REEQUILIBRIO AUTOMÁTICO (Keeper)
    // ------------------------------------------------
    /**
     * @dev checkUpkeep y performUpkeep son ejemplos de la interfaz Keeper:
     *      - Determina si la posición está fuera de rango para reequilibrar.
     *      - Requiere un bot externo (Chainlink Automation, Gelato, etc.).
     */
    function checkUpkeep(bytes calldata) external view returns (bool upkeepNeeded, bytes memory) {
        if (uniswapPositionTokenId != 0) {
            // Aquí podría verificarse el rango, el precio, etc.
            upkeepNeeded = false; // Se cambia a true cuando haya que reequilibrar
        }
        return (upkeepNeeded, bytes(""));
    }

    function performUpkeep(bytes calldata) external whenNotPaused {
        // Ejemplo: Lógica de reequilibrio
        // 1) retirar liquidez si el precio salió del rango
        // 2) recolocar liquidez en un nuevo rango
        // ...
        emit ReequilibrioAutomatizado(block.timestamp);
    }

    // ------------------------------------------------
    // FUNCIÓN DE LIQUIDACIÓN FINAL
    // ------------------------------------------------
    function liquidarContrato() external onlyOwner {
        // Solo cuando pase la fechaFin
        require(block.timestamp > fechaFin, "No ha finalizado el contrato");

        // Si hay una posición activa, retirar liquidez total
        if (uniswapPositionTokenId != 0) {
        // 1) Consultar la liquidez total de la posición
        (
            , 
            , 
            uint128 liquidity, // Ésta es la liquidez actual
            , 
            ,
            ,
            ,
    
        ) = positionManager.positions(uniswapPositionTokenId);
    
        // 2) Llamar a la función que retira esa liquidez
        retirarLiquidezUniswap(liquidity);
        }


        // Transferir todos los USDC a WALLET_OWNER
        uint256 saldoUSDC = usdcToken.balanceOf(address(this));
        if (saldoUSDC > 0) {
            usdcToken.safeTransfer(WALLET_OWNER, saldoUSDC);
        }

        // Transferir todo el otroToken a WALLET_OWNER
        uint256 saldoOT = otroToken.balanceOf(address(this));
        if (saldoOT > 0) {
            otroToken.safeTransfer(WALLET_OWNER, saldoOT);
        }

        // Pausar el contrato para que nadie pueda usarlo más
        _pause();

        emit ContratoLiquidado(saldoUSDC, saldoOT);
    }

    // ------------------------------------------------
    // FUNCIONES DE ADMINISTRACIÓN Y PAUSA
    // ------------------------------------------------
    function pausar() external onlyOwner {
        _pause();
    }

    function despausar() external onlyOwner {
        _unpause();
    }

    // ------------------------------------------------
    // UTILS / LECTURAS
    // ------------------------------------------------
    function totalDepositos() public view returns (uint256) {
        return usdcToken.balanceOf(address(this));
    }
}

				
			

Claves para saber si un NFT (o cualquier token) es un “security token” en Europa:

  1. Derechos financieros o políticos equiparables a los de una acción o participación:

    • El token concede derechos de voto en una entidad, derechos económicos como dividendos, etc.
  2. Participación en los beneficios de terceros:

    • Existe un gestor centralizado que toma decisiones y reparte ganancias.
  3. Expectativa de beneficios por la acción de un promotor (“Howey Test” en EE. UU., aunque no sea la norma europea, a veces se menciona como referencia).

  4. Emisión y comercialización dirigidas a inversores de forma que se parezca a una oferta de valores (por ejemplo, ICOs que prometen rendimientos o participación en una sociedad).

En Uniswap v3, el NFT:

  • No representa una participación en la propiedad o acciones de una empresa.
  • No da derechos de voto en ninguna entidad central.
  • No garantiza una retribución fija ni depende directamente de un promotor central que gestione las ganancias; más bien, la retribución en forma de comisiones depende de la actividad de intercambio de los usuarios y de la liquidez que uno mismo aporta.
  • El usuario aporta los fondos (liquidez) directamente a un smart contract descentralizado y asume el riesgo de impermanent loss y la volatilidad del mercado.

Por tanto, en principio, un NFT de Uniswap v3 no encaja en la categoría de “security token” conforme al criterio europeo habitual, siempre y cuando se use únicamente como “prueba” de la posición de liquidez que un usuario tiene en el pool y no otorgue derechos equiparables a valores negociables tradicionales.

Nuestra newsletter

Que no te lo cuenten. Sé el primero en estar al día en trading y criptomonedas.