Flujos de tokens, NFT y contratos inteligentes · Lección 4 de 6
Swaps en DEX y flujos de liquidez
9 min de lectura
Los exchange descentralizados son lugares donde los tokens robados se convierten en ETH o stablecoins, donde los rug pulls del día del lanzamiento dejan a los compradores sin fondos y donde los bots extraen valor de los traders comunes. Cada swap deja evidencia precisa: el evento propio del pool, las transferencias de tokens que lo rodean y las reservas del pool antes y después. Esta lección muestra cómo leer esa evidencia, reconstruir lo que realmente hizo un trader a través de un agregador, medir el impacto en el precio y reconocer patrones de MEV.
Pools, token0 y token1
Un par al estilo de Uniswap v2 es un contrato que contiene dos tokens. El token con la dirección más baja es token0 y el otro es token1; este orden queda fijado al momento de la creación y no tiene nada que ver con cuál token es «el precio». Cada evento informa los montos en este orden, por lo que un decodificador debe consultar token0, token1 y los decimales de ambos antes de convertir nada.
El evento Swap de v2 es Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to), topic0 0xd78ad95f…d822. Después de cada cambio en las reservas, el par también emite Sync(uint112 reserve0, uint112 reserve1) con los nuevos saldos. Un swap decodificado en un par donde token0 es USDC (6 decimales) y token1 es WETH (18 decimales):
{
"event": "Swap",
"pair": "0x3f8a…e2d1",
"sender": "0x7a2c…48d0",
"to": "0xa1b2c3d4…9f0e",
"amount0In": "0",
"amount1In": "5000000000000000000",
"amount0Out": "14880819116",
"amount1Out": "0",
"next_log": {"event": "Sync", "reserve0": "2985119180884", "reserve1": "1005000000000000000000"}
}
Lectura: entraron 5 WETH y salieron 14.880,819116 USDC, a un precio de ejecución de 14.880,82 ÷ 5 = 2.976,16 USDC por WETH. Por lo general, sender es un contrato router, no el trader; to es el destino de la salida. El trader es quien finalmente envió la entrada y recibió la salida, lo que se confirma con las transferencias de tokens de la misma transacción.
Pools de liquidez concentrada y otros diseños
Los pools de Uniswap v3 emiten Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick), topic0 0xc42079f9…ca67. Los montos llevan signo desde el punto de vista del pool: un valor positivo significa que el token entró al pool y uno negativo, que salió. Un swap de 2 WETH por USDC en un pool USDC/WETH muestra amount0 = -5994120000 (salieron 5.994,12 USDC) y amount1 = 2000000000000000000 (entraron 2 WETH).
El evento también proporciona el precio del pool después del swap, codificado como una raíz cuadrada en formato de punto fijo:
price_raw = (sqrtPriceX96 / 2^96)^2 -- unidades base de token1 por unidad base de token0
price = price_raw * 10^(decimals0 - decimals1)
example : sqrtPriceX96 = 1446501726624926496477173928747177
price_raw = 333.333.333,33 -> price = 0,000333333 WETH por USDC
inverse = 3.000 USDC por WETH (tick ≈ 196.256, ya que price_raw = 1.0001^tick)
El valor al cuadrado está en unidades base, por lo que el ajuste decimal (6 − 18 = −12) es esencial; si se omite, se obtiene un precio que difiere por un factor de 10^12.
Los demás diseños requieren sus propios decodificadores. Uniswap v4 (2025) mantiene todos los pools dentro de un único contrato PoolManager, por lo que la address del registro ya no identifica el pool; lo identifica el id indexado del pool. Su evento Swap, Swap(bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee) (topic0 0x40e9cecb…112f), expresa los montos desde el punto de vista del usuario, lo opuesto a v3, como advierte la guía de datos de Uniswap Foundation. Los pools de Curve emiten TokenExchange con índices de monedas en lugar de direcciones de tokens. Mantén un decodificador por protocolo y versión, y prueba cada uno con una transacción cuyo resultado conozcas.
Rutas a través de agregadores
Un agregador divide una orden entre pools y cadenas de pools en una sola transacción. Los eventos de los pools muestran cada tramo; las transferencias de tokens muestran el resultado neto del trader. Una ruta decodificada, con el router reteniendo los fondos entre tramos:
| logIndex | Token | Desde | Hacia | Monto |
|---|---|---|---|---|
| 10 | WETH | Trader | Router | 10 |
| 11 | WETH | Router | Pool A (WETH/USDC) | 6 |
| 12 | USDC | Pool A | Router | 17.880 |
| 13 | WETH | Router | Pool B (WETH/DAI) | 4 |
| 14 | DAI | Pool B | Pool C (DAI/USDC) | 11.930 |
| 15 | USDC | Pool C | Router | 11.925 |
| 16 | USDC | Router | Trader | 29.805 |
Al compensar las entradas y salidas de cada dirección, todo se reduce a una sola afirmación: el trader vendió 10 WETH por 29.805 USDC, a un promedio de 2.980,50 USDC por WETH. El neto del router y de los pools intermedios es cero, salvo por los cambios propios de cada pool.
-- Cambio neto de tokens por dirección dentro de una transacción
SELECT address, token_contract, SUM(delta) AS net_raw
FROM (
SELECT to_address AS address, token_contract, value_raw AS delta
FROM token_transfers WHERE tx_hash = '0x9e1d…b7a4'
UNION ALL
SELECT from_address, token_contract, -value_raw
FROM token_transfers WHERE tx_hash = '0x9e1d…b7a4'
) t
GROUP BY address, token_contract
HAVING SUM(delta) <> 0;
La consulta suma todas las transferencias entrantes y resta todas las salientes por dirección y token, y conserva solo los resultados distintos de cero. Los tramos de ETH nativo no aparecen en las transferencias de tokens y deben añadirse a partir de las trazas. En los sistemas basados en intención, quien envía la transacción es un solver, no el usuario, por lo que tx.from identifica al solver; al usuario se lo identifica a partir de los eventos de trading del contrato de liquidación y de las transferencias desde y hacia la dirección del usuario.
Adiciones y retiros de liquidez
Los eventos de liquidez revelan quién financia un mercado y quién retira esos fondos.
| Protocolo | Adición | Retiro | Registro de propiedad |
|---|---|---|---|
| Uniswap v2 | Mint(sender, amount0, amount1) más una transferencia de tokens LP desde la dirección cero | Tokens LP enviados al par, Burn(sender, amount0, amount1, to), transferencia de LP a la dirección cero | Token LP fungible (el contrato del par) |
| Uniswap v3 | Mint del pool con rango de ticks; IncreaseLiquidity en el administrador de posiciones | Burn del pool (acredita tokens) y luego Collect (los paga); DecreaseLiquidity | NFT de posición (ERC-721) |
| Uniswap v4 | ModifyLiquidity en el PoolManager | El mismo evento, con delta de liquidez negativo | Registros de posiciones, a menudo un NFT |
Dos trampas. En v3, el Mint del pool muestra el contrato administrador de posiciones como propietario, por lo que el verdadero proveedor de liquidez es quien posee el NFT de posición; sigue el NFT. Además, un Burn de v3 no mueve tokens; el dinero sale en Collect. Un patrón clásico de rug pull es el de un deployer que crea un token, añade liquidez, espera a los compradores y luego retira casi toda la liquidez en una sola transacción; los montos de Burn o DecreaseLiquidity miden cuánto se retiró.
Cálculo del impacto en el precio
El impacto en el precio compara el precio de ejecución con el precio spot del pool justo antes de la operación. Para un par v2, las reservas anteriores al swap equivalen a las reservas de su evento Sync menos las entradas más las salidas: aquí, 2.985.119,18 + 14.880,82 = 3.000.000 USDC y 1.005 − 5 = 1.000 WETH, con un precio spot de 3.000.
| Medida | Valor |
|---|---|
| Precio spot antes | 3.000,00 USDC por WETH |
| Precio de ejecución | 2.976,16 (−0,79 %) |
| Salida sin la comisión de 0,30 % | 14.925,37, precio 2.985,07 (−0,50 %) |
| Precio spot después (según Sync) | 2.970,27 (−0,99 %) |
Por lo tanto, de la diferencia de 0,79 %, alrededor de 0,30 % corresponde a la comisión y alrededor de 0,50 % al impacto puro en el precio. El precio spot posterior a la operación se movió el doble que el precio promedio pagado, lo cual es normal en los pools de producto constante. Un impacto grande en un pool con poca liquidez es una señal de venta forzada, por ejemplo, cuando un atacante vuelca un token robado.
Identificación de patrones de MEV y sandwich
La actividad de valor máximo extraíble (MEV) presenta patrones reconocibles en los datos de los bloques:
- Sandwich: en un mismo bloque y un mismo pool, el actor X compra el token T, luego una víctima compra T y, justo después, X vende T. Las tres transacciones suelen ser contiguas por índice; la cantidad que vende X es aproximadamente igual a la que compró, y X suele pagar directamente al creador del bloque (una transferencia de ETH al destinatario de las comisiones del bloque, visible en las trazas).
- Arbitraje atómico: una transacción realiza swaps a través de un ciclo de pools (WETH → USDC → DAI → WETH) y termina con más unidades del token inicial.
- Liquidez just-in-time (JIT): un
Mintde v3 inmediatamente antes de un swap grande, y unBurnyCollectinmediatamente después, realizados por la misma dirección. - Liquidaciones: se tratan en la lección Préstamos, puentes y contabilidad de protocolos.
-- Posibles sandwich: mismo bloque y pool, abiertos y cerrados por un solo actor
SELECT f.block_number, f.pool, f.actor, v.tx_hash AS victim_tx
FROM dex_swaps f
JOIN dex_swaps v
ON v.block_number = f.block_number AND v.pool = f.pool
AND v.tx_index > f.tx_index AND v.token_bought = f.token_bought
JOIN dex_swaps b
ON b.block_number = f.block_number AND b.pool = f.pool
AND b.tx_index > v.tx_index AND b.actor = f.actor
AND b.token_sold = f.token_bought
WHERE v.actor <> f.actor;
La consulta busca una operación anticipada (f), una víctima (v) que compre el mismo token más adelante en el bloque y una operación posterior (b) en la que el mismo actor lo venda. Define actor como el contrato del bot que recibió el resultado del swap y no como el remitente de la transacción, porque los bots de sandwich suelen rotar las direcciones desde las que envían transacciones. Considera los resultados como candidatos: comprueba las cantidades, las ganancias y que no haya una explicación inocente, como dos participantes de arbitraje sin relación entre sí.
Enfoque del examen: orden de token0/token1 y decimales; decodificación de los eventos Swap (amountIn/amountOut) y Sync de Uniswap v2; montos con signo en v3 (positivo significa que entran al pool) y conversión de sqrtPriceX96 a un precio con el ajuste decimal; singleton PoolManager, pool id y convención de signos invertida de v4; compensación de transferencias para encontrar el resultado de un trader a través de un agregador y por qué tx.from puede ser un router o un solver; eventos de liquidez de v2 y v3, NFT de posición y patrón Burn seguido de Collect; cálculo del impacto en el precio a partir de las reservas; patrones de sandwich, arbitraje atómico y liquidez JIT, así como sus falsos positivos.