Integration guide
The deposit → stream → redeem lifecycle, with both the classic on-chain path
and the gasless macro path. These mirror vaultTxs.ts,
useBatchedDeposit.ts, and useBatchedRedeem.ts in the frontend.
Requirements
- Base mainnet, USDC for deposits.
- The vault address (and, for gasless flows, the
SyncVaultMacroaddress). - A gas buffer: the frontend multiplies estimated gas by 2×
(
TX_GAS_BUFFER) for deposit / redeem /connectPool, because the Superfluid Host → Morpho → Aave call stack is deep and the EVM 63/64 gas rule can starve inner calls otherwise.
Lifecycle at a glance
- Deposit — capital in, units granted, stream starts, member connected.
- Stream — yield flows every second; read it from the pool.
- Redeem — shares burned, principal + surplus paid out pro-rata.
The key design point: the stream starts at deposit time, not at a later claim. And there is no principal decrement on redeem — payout is pure pro-rata, so remaining holders stay whole.
Classic deposit (on-chain)
// 1. Approve the exact amount
USDC.approve(vault, assets)
// 2. Deposit (use a ~2x gas buffer)
vault.deposit(assets, receiver)
Inside deposit, the FundManager's onDeposit hook:
- grants
toUnit(assets)GDA pool units to the receiver, - rebalances yield assets (pull deficit from / push surplus to the Morpho vault),
- upgrades residual USDC to the USDCx reserve (pre-funding the GDA buffer),
- deposits the remainder into the Morpho vault,
- recalibrates the flow rate so the stream starts immediately.
Confirm via the Deposit event (shares minted in the same block). Note the
member is not auto-connected on this path — call connectPool separately if
you want the stream counted in the wallet balance.
Classic redeem (on-chain)
// Optionally read the cap first
maxRedeem(owner)
vault.redeem(shares, receiver, owner) // ~2x gas buffer
onWithdraw pays redeemingAssets (shares × totalAssets / supply, floored) in
priority order: (1) resting raw underlying, (2) a shares-proportional slice of
the USDCx reserve (downgraded), (3) the Morpho vault for the remainder. Then it
rebalances and recalibrates the (now lower) flow. Decode the exact payout from
the Withdraw event — the pre-tx previewRedeem is only an estimate.
Gasless deposit (macro + relayer)
Requires the SyncVaultMacro. The user signs; a relayer pays gas. One batched
transaction does deposit and pool-connect.
- Permit (EIP-2612) — user signs allowance for the vault to pull
assetsgross (fee-inclusive). Build the domain from USDCnonces(owner)andname()(Base USDC uses version"2"). - Macro signature (EIP-712) — user signs a
SyncVaultDepositAndConnecttyped message. Action fields:description(read from the macro),assets,deadline. Security fields:provider,macroContract,validAfter/validBefore(~300s relay window),nonce. - Relay — POST
encodeParams(actionParams, security)+ signature to the relayer; it callsClearMacroForwarder.runMacro(...). Via EIP-2771 the user ismsg.senderinside the Host. - Result — the macro batches
depositWithPermit()+connectPool(); units granted, stream started, and the member connected in one tx.
Relevant macro helpers: encodeDepositAndConnect(lang, assets, deadline, v, r, s)
and describeDepositAndConnect(lang, assets) (the human-readable description is
baked into the signed digest).
Gasless redeem (macro + relayer)
Simpler — no permit, because redeem(shares, signer, signer) burns the signer's
own shares.
- Macro signature (EIP-712) — user signs a
SyncVaultRedeemmessage:description,shares,deadline, plus the same security envelope (~300svalidBeforewindow is the real bound). - Relay — POST
encodeParams(actionParams, security)+ signature; relayer submits. - Result — shares burned, principal + surplus returned. Re-fetch the
receipt and decode the
Withdrawevent for the exact assets received.
Helpers: encodeRedeem(lang, shares, deadline) and
describeRedeem(lang, shares).
Invariants worth respecting
- Units are nominal. Granted on principal contributed, not on NAV. External surplus accrues to share value, never to units.
- No double-counting. Yield = promised stream plus external surplus, kept separate.
- Spot pricing.
previewRedeemis live spot (no TWAP) — the external source must have non-manipulable pricing. - First-deposit attack is mitigated by a
_decimalsOffset()of 12. - Liquidity. Rebalance deficit pulls can revert if Morpho liquidity is short;
forceDeallocateis the operator lever to unstick.
Status
Everything described here — deposit fee, macro/gasless flows, deposit-time stream pre-funding, external pause, operator solvency upkeep — is implemented in the contracts the frontend targets. The repo's subgraph package is still a stub awaiting finalized vault events, so don't depend on it yet.
See also
- Architecture for the design rationale.
- Reading vault state for the view calls.