Um yield aggregator é um problema de custódia com cara de produto. Um depósito de USDC, quatro vaults ERC-4626 ao vivo, pesos que o usuário possui. O contrato precisa tornar esse split atômico, reversível e impossível de deixar preso.
Este é o terceiro marco da nossa série de produção com Stylus. A Parte 1 perguntava como coordenar múltiplas fontes de liquidez sob condições reais de mercado. A Parte 2 perguntava como impedir que um oráculo assíncrono deixe fundos presos. Desta vez a pergunta é outra: como pegar um depósito, dividi-lo entre quatro vaults de produção em uma única transação, deixar o usuário dono da alocação e ainda garantir uma saída depois que um vault for desabilitado?
Veja o padrão, não o produto
A superfície é o Vaulty: um yield aggregator de USDC embarcado como Mini App. O problema de engenharia é um router sobre vaults ERC-4626 ao vivo que não pode deixar uma posição parcial, deixar um owner mover o dinheiro de outra pessoa, nem prender fundos em um vault que depois é desligado. Este walkthrough é o fluxo ao vivo: escolher weights, depositar, rebalancear ou sair.
Referência open source
Contratos Stylus, adapters ERC-4626, periphery Permit2, share math e o runbook de mainnet. Estude o padrão, faça fork, estenda.
- Rust
- Arbitrum Stylus
- ERC-4626
- Open source
Walkthrough ao vivo
End-to-end do Vaulty: escolher weights, depositar USDC, rebalancear e sair across Aave, Morpho, Fluid e Euler. Não é um mock.
- Arbitrum One
- USDC
- E2E
Implementação de referência, não um produto de consumidor que operamos. Este post descreve uma arquitetura open source que a WakeUp Labs publicou para o ecossistema Stylus. A Mini App é uma superfície de distribuição. Os contratos são a referência.
Por que esse problema, depois de DEX aggregation e aleatoriedade
Um DEX aggregator falha em voz alta. Quotes erram, rotas revertem, o usuário tenta de novo. Um app de aleatoriedade falha se o callback nunca chega, por isso a Parte 2 gastou sua energia no gap entre o request e a resposta.
Um vault aggregator falha em silêncio. Um adapter reverte e três legs já se moveram. Um owner desabilita um protocolo e um usuário ainda segura shares lá. O primeiro depositor é diluído por um inflation attack. A estimativa de gas subreporta uma chamada Stylus e a transação minera out of gas com dinheiro in flight.
Isso não são features de produto. São as razões pelas quais a maioria dos demos de “yield em um clique” nunca vira software de produção.
Arquitetura: um ledger, quatro adapters, zero privilégio na porta
Se você lembrar de um diagrama, que seja este: o core é dono do share ledger per-user. Cada adapter é um wrapper ERC-4626 fino, de um único vault. O periphery é uma porta Permit2 sem nenhum privilégio que o core reconheça.
User / Mini App
|
v
[ periphery ] -- porta Permit2 sem 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 ao vivo na Arbitrum Onevault-coreO system of record. É dono do share ledger per-user, per-adapter, do registry de adapters e da matemática de split / rebalance / redeem. Não há controle de alocação no nível do owner. O trabalho do owner é manutenção do registry: add_adapter e set_enabled. Nunca alocação.
vault-peripherySem estado de propósito. Puxa USDC via um Permit2 SignatureTransfer de uso único e chama depositFor. Um periphery comprometido no máximo pode doar o próprio saldo transitório. Não consegue mintar um claim sem lastro contra o USDC custodiado de outros usuários.
vault-adapterUm binário Stylus, quatro instâncias deployed. Nunca uma instância servindo vários vaults. init(vault, core) é one-shot. Chamadas que mutam são onlyCore. O adapter custodia os vault shares; o core nunca os hold.
mock-vaultUm ERC-4626 de manual usado só na Arbitrum Sepolia, para exercitar o plumbing do core sem tocar dinheiro real de protocolos. O comportamento real foi validado contra Aave, Morpho, Fluid e Euler na Arbitrum One.
Regra de design: se um contrato pode hold o claim de um usuário, ele é core. Adapters hold vault shares em nome do core e nada mais. O periphery não tem privilégio nenhum.
A parte difícil é o split
O usuário acha que está escolhendo uma alocação: 40% Aave, 30% Morpho, 20% Fluid, 10% Euler. O contrato precisa transformar essa alocação em uma única transição de estado atômica, e depois mantê-la verdadeira across rebalance, disable e exit.
t0 rebalance(weights) -- só o usuário. Unwind, medir, guardar, re-split.
t1 deposit(usdc) -- snapshot de total_assets, split por bps, um deposit por leg.
-- um adapter que falha reverte a tx inteira.
... a posição vive como shares per-adapter dentro do core ...
t2 redeem(bps) -- sai uma fração da posição do caller para USDC no core.
t3 Mini App / wallet -- o segundo passo move esse USDC para a conta do usuário.Atômico na entrada, two-step na saída
O depósito é atômico no nível da transação inteira: um único adapter que falha reverte o depósito inteiro em vez de deixar uma posição parcial. O withdraw é two-step por design. redeem(bps) sai uma fração da posição própria do caller, basis points dessa posição, não um share count cru, porque cada adapter tem o próprio share price, de volta para USDC custodiado pelo vault-core. O segundo passo, mover esse USDC para a conta Lemon do usuário, vive na Mini App, não no contrato.
O rebalance é só do usuário
Chamar rebalance(new_weights) faz unwind completo da posição atual do caller across cada adapter em que ele segura shares, inclusive adapters que o owner depois desabilitou. Mede os proceeds reais em USDC, guarda o novo set de weights e re-splita esses proceeds contra ele. Não há um path de admin que possa fazer isso em nome de outra pessoa.
Escolha uma falha. Veja o que o contrato faz.
Estes são os casos para os quais realmente desenhamos. A state machine precisa ser entediantemente correta em cada um.
1. Um adapter reverte no meio do depósito
O path de depósito faz snapshot de total_assets() de cada adapter ativo uma vez, divide o amount de entrada por basis points e empurra um deposit por leg. Se qualquer leg falha, a transação inteira reverte. Posições parciais não são um estado representável.
2. O owner desabilita um vault que o usuário ainda hold
Não há remove_adapter. Essa função foi apagada de propósito: o guard que existia para enforceá-la só existia porque a função existia. Desabilitar um adapter bloqueia dinheiro novo de escolhê-lo como weight target. Nunca bloqueia dinheiro para fora. Tanto redeem quanto o unwind de rebalance iteram o registry completo filtrado por shares holded, não pelo enabled set. Um adapter desabilitado não consegue prender uma posição.
3. Alguém doa tokens para um adapter
Adapters não têm sweep(), nem rescue(), nem recovery privilegiado. USDC nativo ou um ERC-20 alheio enviado direto para um adapter fica lá para sempre, inclusive da nossa parte. Vault shares doados sim aumentam total_assets(), é um presente, não um exploit, e a defesa contra inflation attack vive no share math do core, não em uma backdoor privilegiada no adapter.
4. O primeiro depositor é diluído
O core mantém um share ledger per-user, per-adapter com um esquema de virtual-offset e floor-rounding: a mitigação padrão de inflation attack do ERC-4626, aplicada per adapter em vez de a um pool agregado. A fórmula vive em um único lugar on-chain e é espelhada off-chain para display. Não há um share scalar global, porque cada vault tem o próprio share price.
5. O nó subreporta gas e a tx minera OOG
Isso nos pegou na Arbitrum One ao vivo, duas vezes, antes mesmo de o core estar deployed. eth_estimateGas subreportou chamadas Stylus. A mesma call teve sucesso via eth_call. A transação minerada usou exatamente o limite estimado e falhou. O withdraw da Morpho caminha uma market queue; é o caro. A regra que enviamos: fixar um gas limit explícito bem acima do estimate. Gas não usado é reembolsado na Arbitrum, então o headroom é de graça. Um writeContract do wagmi que confia na estimativa automática vai reproduzir isso e vai parecer um bug do contrato.
Como os fundos entram: Permit2 para smart accounts, approve para o resto
A smart-account wallet da Lemon não consegue fazer um fluxo de duas transações de approve + deposit. O periphery existe por causa dessa constraint, a mesma que a Parte 2 já tinha contornado.
| Path | Como parece | Por que existe |
|---|---|---|
Mini App SignatureTransfer | Um permit EIP-712 de uso único por depósito. Sem approval prévia. | A Lemon assina o struct de Permit2 no client e submete com suporte nativo de permits[]. |
Clássico approve + deposit | O path chato. | Qualquer EOA pode chamar vault-core::depositFor direto. O periphery é opcional. |
Cabendo no WASM gate
A Arbitrum One aplica um limite de 24KB de WASM comprimido por fragmento de contrato. Na prática o teto fica mais perto de 22KB, porque o ArbOS na Arbitrum One não suporta programas multi-fragment. Cada contrato neste repo precisa passar nesse gate.
A receita de build é a que já comprometemos na Parte 2: perfil de release na raiz do workspace (opt-level = "z", LTO, panic = "abort", strip) mais um pass nightly de -Cpanic=immediate-abort / -Zbuild-std para a redução final de tamanho. Os quatro adapters são quatro instâncias do mesmo WASM, ligados a endereços de vault diferentes no init. Um blueprint, quatro deploys, nunca uma instância servindo vários vaults.
O que dá para observar de verdade
O vault-core e o vault-periphery de produção estão ao vivo na Arbitrum One desde 10 de agosto de 2026, com quatro instâncias de adapter apontadas para Morpho gtUSDCc, Fluid fUSDC, Euler eUSDC-2 e Aave stataArbUSDCn.
O smoke test de mainnet rodou um ciclo completo contra USDC real e vaults reais: setar weights 40/30/20/10, depositar 1 USDC, confirmar que os shares aterraram nessa ratio, rebalancear para 10/20/30/40, redeem 100%. Custo líquido do ciclo inteiro: 7 units, sete milionésimos de dólar, de round-down ERC-4626. Todos os totais de shares de adapter de volta a zero depois do exit.
Gas medido, que agora calibra cada client: deposit 1,604,156 · redeem 1,741,442 · rebalance que seta weights 318,244 · rebalance completo 2,607,088. Um limite de 2,000,000 fez OOG no primeiro rebalance real. Scripts e frontend agora pinam 6,000,000.
As três propriedades que generalizam
1. Tornar o sucesso parcial irrepresentável. Se o split não consegue completar, nada se move.
2. Dar ao usuário o único path que realoca o dinheiro dele. Registry é admin. Alocação não é.
3. Desabilitar um vault nunca pode prender uma posição. Dinheiro para fora é independente do enabled set.
A superfície específica aqui é uma Mini App de yield. O padrão não. Qualquer router que abre um único depósito em vários protocolos externos, vaults, lending markets, produtos estruturados, tem esse formato. Construir isso no Stylus nos deixou expressar o share ledger per-adapter, o rebalance de unwind-then-resplit e o periphery sem privilégio em Rust, com o limite de tamanho WASM e o comportamento de gas ao vivo forçando tradeoffs reais em vez de teóricos.
O que vem a seguir
Este é o terceiro marco de uma série open source que valida o Stylus com implementações de referência de produção. A Parte 1 cobriu DEX aggregation. A Parte 2 cobriu aleatoriedade verificável. Cada marco responde a uma pergunta de engenharia diferente, e todos enviam fully open source para que outros times construindo no Stylus possam estudar, fazer fork e estender o padrão.
Anunciamos essa iniciativa no início do ano como três Mini Apps de produção na Arbitrum. Vaulty é a utility DeFi desse conjunto: um allocator user-facing em cima de vaults ERC-4626 ao vivo, rodando em um ambiente production-like para que a performance do Stylus apareça onde realmente importa.
Source completo, notas de arquitetura e o runbook de deploy: github.com/wakeuplabs/ArbitrumMiniApp-3-Vaulty.
Se você está construindo um router Stylus que precisa dividir, rebalancear e sair contra vaults de protocolos ao vivo, gostaríamos de comparar notas.
Sobre a WakeUp Labs
Na WakeUp Labs, construímos infraestrutura blockchain e produtos digitais de grau produtivo para ecossistemas líderes, startups e empresas.
Nossa filosofia de engenharia é simples: infraestrutura nova só prova valor quando roda em produção. Em vez de avaliar tecnologias emergentes com protótipos isolados, validamos construindo sistemas reais que interagem com usuários reais e restrições operacionais reais.
Se você está construindo na Arbitrum ou explorando Stylus para sistemas de produção, adoraríamos conectar.