Skip to main content

Scenario

A user holds WETH and wants yield-bearing Aave USDC (aEthUSDC) on Ethereum mainnet. Two operations are needed: a swap to convert WETH into USDC, and a zap to deposit that USDC into Aave. The compose stack expresses this as a two-node Flow where the swap’s amountOut handle is threaded directly into the zap’s amountIn binding — no manual bookkeeping, no intermediate transfer. This recipe is the canonical shape for chained swap → zap flows: a routing-provider swap followed by a protocol-specific zap, both lowered to VM instructions by the backend.

What this recipe demonstrates

  • Threading an op’s typed output handle into a downstream op’s input (swapOutputs.amountOutzap.amountIn).
  • Running a lifi.swap without a slippage guard (the swap’s amountOut port carries providesMinimum, so slippage is enforced by the provider).
  • Attaching a slippage guard on the lifi.zap output (see the guards concept page).
  • Using materialisers.directDeposit to fund the flow with a fixed amount, and sweepTo: builder.context.sender to return any residual tokens.

Full example

Adapted from swapAndZap.ts in the composer-sdk-examples repo.

What to observe

  • Inputs. One resource input (amountIn: WETH). The directDeposit materialiser transfers exactly 10^18 wei (1 WETH) into the VM; no balance read is performed.
  • Nodes. Exactly two: flow.nodes[0].op === 'lifi.swap', flow.nodes[1].op === 'lifi.zap'.
  • Handle threading. The zap’s bind.amountIn is the ref swap.amountOut — no intermediate resource or transfer.
  • Guards. Only the zap carries a slippage guard (port: 'amountOut', bps: 100, i.e. 1%). The swap has no guard because its amountOut port already provides a minimum.
  • Terminal resource. The Aave aEthUSDC produced by the zap is the terminal resource and is swept to the sender.
  • producedResources[<name>].simulated?.amountOut. On the ComposeCompileResult, producedResources includes the zap’s output with the simulated aEthUSDC amount at .simulated?.amountOut.