Un yield aggregator es un problema de custodia con cara de producto. Un depósito de USDC, cuatro vaults ERC-4626 en vivo, pesos que el usuario posee. El contrato tiene que hacer ese split atómico, reversible e imposible de dejar varado.
Este es el tercer hito de nuestra serie de producción con Stylus. La Parte 1 preguntaba cómo coordinar múltiples fuentes de liquidez bajo condiciones reales de mercado. La Parte 2 preguntaba cómo impedir que un oráculo asíncrono deje fondos varados. Esta vez la pregunta es distinta: ¿cómo tomar un depósito, dividirlo entre cuatro vaults de producción en una sola transacción, dejar que el usuario sea dueño de la allocation y igual garantizar un exit después de que un vault se deshabilite?
Ver el patrón, no el producto
La superficie es Vaulty: un yield aggregator de USDC embebido como Mini App. El problema de ingeniería es un router sobre vaults ERC-4626 en vivo que no puede dejar una posición parcial, dejar que un owner mueva la plata de otro, ni atrapar fondos en un vault que después se apaga. Este recorrido es el flujo en vivo: elegir weights, depositar, rebalancear o salir.
Referencia open source
Contratos Stylus, adapters ERC-4626, periphery Permit2, share math y el runbook de mainnet. Para estudiar el patrón, hacer fork y extenderlo.
- Rust
- Arbitrum Stylus
- ERC-4626
- Open source
Recorrido en vivo
End-to-end de Vaulty: elegir weights, depositar USDC, rebalancear y salir across Aave, Morpho, Fluid y Euler. No es un mock.
- Arbitrum One
- USDC
- E2E
Implementación de referencia, no un producto de consumidor que operamos. Este post describe una arquitectura open source que WakeUp Labs publicó para el ecosistema Stylus. La Mini App es una superficie de distribución. Los contratos son la referencia.
Por qué este problema, después de DEX aggregation y aleatoriedad
Un DEX aggregator falla en voz alta. Las quotes fallan, las rutas revierten, el usuario vuelve a intentar. Una app de aleatoriedad falla si el callback nunca llega, por eso la Parte 2 gastó su energía en el gap entre el request y la respuesta.
Un vault aggregator falla en silencio. Un adapter revierte y tres legs ya se movieron. Un owner deshabilita un protocolo y un usuario todavía tiene shares ahí. El primer depositor se diluye por un inflation attack. El gas estimation sub-reporta una llamada Stylus y la transacción minea out of gas con plata in flight.
Eso no son features de producto. Son las razones por las que la mayoría de los demos de “yield en un click” nunca se vuelven software de producción.
Arquitectura: un ledger, cuatro adapters, cero privilegio en la puerta
Si te queda un diagrama, que sea este: el core es dueño del share ledger per-user. Cada adapter es un wrapper ERC-4626 delgado, de un solo vault. El periphery es una puerta Permit2 sin ningún privilegio que el core reconozca.
User / Mini App
|
v
[ periphery ] -- puerta Permit2 sin estado. Opcional.
|
v
[ core ] -- share ledger. Split. Rebalance. Redeem.
| | | |
v v v v
[adapter] [adapter] [adapter] [adapter]
Aave Morpho Fluid Euler
| | | |
v v v v
vaults ERC-4626 en vivo en Arbitrum Onevault-coreEl system of record. Es dueño del share ledger per-user, per-adapter, del registry de adapters y de la matemática de split / rebalance / redeem. No hay perilla de allocation a nivel owner. El trabajo del owner es mantenimiento del registry: add_adapter y set_enabled. Nunca allocation.
vault-peripherySin estado a propósito. Mete USDC via un Permit2 SignatureTransfer de un solo uso y llama depositFor. Un periphery comprometido como máximo puede donar su propio balance transitorio. No puede mintear un claim sin respaldo contra el USDC custodiado de otros usuarios.
vault-adapterUn binario Stylus, cuatro instancias deployed. Nunca una instancia sirviendo varios vaults. init(vault, core) es one-shot. Las llamadas que mutan son onlyCore. El adapter custodia los vault shares; el core nunca los hold.
mock-vaultUn ERC-4626 de manual usado solo en Arbitrum Sepolia, para ejercitar el plumbing del core sin tocar plata real de protocolos. El comportamiento real se validó contra Aave, Morpho, Fluid y Euler en Arbitrum One.
Regla de diseño: si un contrato puede hold el claim de un usuario, es core. Los adapters hold vault shares en nombre del core y nada más. El periphery no tiene privilegio alguno.
La parte difícil es el split
El usuario cree que está eligiendo una allocation: 40% Aave, 30% Morpho, 20% Fluid, 10% Euler. El contrato tiene que convertir esa allocation en una sola transición de estado atómica, y después mantenerla verdadera across rebalance, disable y exit.
t0 rebalance(weights) -- solo el usuario. Unwind, medir, guardar, re-split.
t1 deposit(usdc) -- snapshot de total_assets, split por bps, un deposit por leg.
-- un adapter que falla revierte toda la tx.
... la posición vive como shares per-adapter adentro del core ...
t2 redeem(bps) -- sale una fracción de la posición del caller a USDC en el core.
t3 Mini App / wallet -- el segundo paso mueve ese USDC a la cuenta del usuario.Atómico de entrada, two-step de salida
El depósito es atómico a nivel transacción completa: un solo adapter que falla revierte todo el depósito en vez de dejar una posición parcial. El withdraw es two-step por diseño. redeem(bps) sale una fracción de la posición propia del caller, basis points de esa posición, no un share count crudo, porque cada adapter tiene su propio share price, de vuelta a USDC custodiado por vault-core. El segundo paso, mover ese USDC a la cuenta Lemon del usuario, vive en la Mini App, no en el contrato.
El rebalance es solo del usuario
Llamar rebalance(new_weights) hace unwind completo de la posición actual del caller across cada adapter en el que tiene shares, incluidos adapters que el owner después deshabilitó. Mide los proceeds reales en USDC, guarda el nuevo set de weights y re-splitea esos proceeds contra él. No hay un path de admin que pueda hacer esto en nombre de otro.
Elegir un fallo. Ver qué hace el contrato.
Estos son los casos para los que realmente diseñamos. La state machine tiene que ser aburridamente correcta en cada uno.
1. Un adapter revierte a mitad del depósito
El path de depósito hace snapshot de total_assets() de cada adapter activo una vez, divide el amount entrante por basis points y pushea un deposit por leg. Si cualquier leg falla, revierte toda la transacción. Las posiciones parciales no son un estado representable.
2. El owner deshabilita un vault que el usuario todavía hold
No hay remove_adapter. Esa función se borró a propósito: el guard que existía para enforcearla solo existía porque la función existía. Deshabilitar un adapter bloquea que plata nueva lo elija como weight target. Nunca bloquea plata hacia afuera. Tanto redeem como el unwind de rebalance iteran el registry completo filtrado por shares holded, no por el enabled set. Un adapter deshabilitado no puede dejar una posición varada.
3. Alguien dona tokens a un adapter
Los adapters no tienen sweep(), ni rescue(), ni recovery privilegiado. USDC nativo o un ERC-20 ajeno mandado directo a un adapter se queda ahí para siempre, incluso de nosotros. Los vault shares donados sí aumentan total_assets(), es un regalo, no un exploit, y la defensa contra inflation attack vive en el share math del core, no en un backdoor privilegiado en el adapter.
4. El primer depositor se diluye
El core mantiene un share ledger per-user, per-adapter con un esquema de virtual-offset y floor-rounding: la mitigación standard de inflation attack de ERC-4626, aplicada per adapter en vez de a un pool agregado. La fórmula vive en un solo lugar on-chain y se replica off-chain para display. No hay un share scalar global, porque cada vault tiene su propio share price.
5. El nodo sub-reporta gas y la tx minea OOG
Esto nos pegó en Arbitrum One en vivo, dos veces, antes de que el core siquiera estuviera deployed. eth_estimateGas sub-reportó llamadas Stylus. La misma call tuvo éxito via eth_call. La transacción minada usó exactamente el límite estimado y falló. El withdraw de Morpho camina una market queue; es el caro. La regla que publicamos: pinear un gas limit explícito bien por encima del estimate. El gas no usado se reembolsa en Arbitrum, así que el headroom es gratis. Un writeContract de wagmi que confíe en la estimación automática va a reproducir esto y va a parecer un bug del contrato.
Cómo entran los fondos: Permit2 para smart accounts, approve para el resto
La smart-account wallet de Lemon no puede hacer un flujo de dos transacciones de approve + deposit. El periphery existe por esa constraint, la misma que la Parte 2 ya había laburado.
| Path | Cómo se siente | Por qué existe |
|---|---|---|
Mini App SignatureTransfer | Un permit EIP-712 de un solo uso por depósito. Sin approval previa. | Lemon firma el struct de Permit2 del lado client y lo submitea con soporte nativo de permits[]. |
Clásico approve + deposit | El path aburrido. | Cualquier EOA puede llamar vault-core::depositFor directo. El periphery es opcional. |
Entrar en el WASM gate
Arbitrum One aplica un límite de 24KB de WASM comprimido por fragmento de contrato. En la práctica el techo está más cerca de 22KB, porque ArbOS en Arbitrum One no soporta programas multi-fragment. Cada contrato de este repo tiene que pasar ese gate.
La receta de build es la que ya comprometimos en la Parte 2: perfil de release en la raíz del workspace (opt-level = "z", LTO, panic = "abort", strip) más un pass nightly de -Cpanic=immediate-abort / -Zbuild-std para la reducción final de tamaño. Los cuatro adapters son cuatro instancias del mismo WASM, cableadas a distintas direcciones de vault en el init. Un blueprint, cuatro deploys, nunca una instancia sirviendo varios vaults.
Lo que se puede observar de verdad
El vault-core y el vault-periphery de producción están live en Arbitrum One desde el 10 de agosto de 2026, con cuatro instancias de adapter apuntadas a Morpho gtUSDCc, Fluid fUSDC, Euler eUSDC-2 y Aave stataArbUSDCn.
El smoke test de mainnet corrió un ciclo completo contra USDC real y vaults reales: setear weights 40/30/20/10, depositar 1 USDC, confirmar que los shares aterrizaron en esa ratio, rebalancear a 10/20/30/40, redeem 100%. Costo neto del ciclo entero: 7 units, siete millonésimas de dólar, de round-down ERC-4626. Todos los totales de shares de adapter de vuelta a cero después del exit.
Gas medido, que ahora calibra cada client: deposit 1,604,156 · redeem 1,741,442 · rebalance que setea weights 318,244 · rebalance completo 2,607,088. Un límite de 2,000,000 hizo OOG el primer rebalance real. Scripts y frontend ahora pinean 6,000,000.
Las tres propiedades que generalizan
1. Hacer el éxito parcial irrepresentable. Si el split no puede completar, no se mueve nada.
2. Darle al usuario el único path que realoca su plata. El registry es admin. La allocation no.
3. Deshabilitar un vault nunca tiene que poder dejar una posición varada. La plata hacia afuera es independiente del enabled set.
La superficie específica acá es una Mini App de yield. El patrón no. Cualquier router que abre un solo depósito en varios protocolos externos, vaults, lending markets, productos estructurados, tiene esta forma. Construirlo en Stylus nos dejó expresar el share ledger per-adapter, el rebalance de unwind-then-resplit y el periphery sin privilegio en Rust, con el límite de tamaño WASM y el comportamiento de gas en vivo forzando tradeoffs reales en vez de teóricos.
Qué sigue
Este es el tercer hito de una serie open source que valida Stylus con implementaciones de referencia de producción. La Parte 1 cubrió DEX aggregation. La Parte 2 cubrió aleatoriedad verificable. Cada hito responde una pregunta de ingeniería distinta, y todos se publican fully open source para que otros equipos que construyen en Stylus puedan estudiar, hacer fork y extender el patrón.
Anunciamos esta iniciativa a principios de año como tres Mini Apps de producción en Arbitrum. Vaulty es la utility DeFi de ese set: un allocator user-facing encima de vaults ERC-4626 en vivo, corriendo en un entorno production-like para que la performance de Stylus se vea donde realmente importa.
Source completo, notas de arquitectura y el runbook de deploy: github.com/wakeuplabs/ArbitrumMiniApp-3-Vaulty.
Si estás construyendo un router Stylus que tiene que dividir, rebalancear y salir contra vaults de protocolos en vivo, nos encantaría comparar notas.
Acerca de WakeUp Labs
En WakeUp Labs construimos infraestructura blockchain y productos digitales de grado productivo para ecosistemas líderes, startups y empresas.
Nuestra filosofía de ingeniería es simple: la infraestructura nueva solo demuestra su valor cuando corre en producción. En lugar de evaluar tecnologías emergentes con prototipos aislados, las validamos construyendo sistemas reales que interactúan con usuarios reales y restricciones operativas reales.
Si estás construyendo en Arbitrum o explorando Stylus para sistemas de producción, nos encantaría conectar.