PLACEHOLDER — Discover pools off-chain, then swap and deposit to Aave atomically on-chain.
1import { ISauceRouter } from "./artifacts/ISauceRouter.json";2import { IERC20 } from "./artifacts/IERC20.json";3import { IUniswapV3Pool } from "./artifacts/IUniswapV3Pool.json";4import { IAavePool } from "./artifacts/IAavePool.json";56// pools: Tuple of Tuples — each pool is [poolType, poolAddress, fee, tickSpacing, hooks]7// Adaptive price-stepping loop: steps sqrtPriceLimitX96 toward the pool prices (down for8// zeroForOne, up for oneForZero) so deep pools swap more and shallow/high-fee pools contribute less.910function main(11 tokenIn: Address,12 tokenOut: Address,13 amountIn: Uint256,14 caller: Address,15 beneficiary: Address,16 aavePool: Address,17 pools: Tuple,18 stepSize: Uint256,19 zeroForOne: Uint256,20): Uint256 {21 const router = ISauceRouter.at(address.self);22 const token = IERC20.at(tokenIn);2324 token.transferFrom(caller, address.self, amountIn);2526 // Pre-swap output balance — supply only the swap delta (this runs in the shared27 // runtime context, so never sweep pre-existing tokenOut dust).28 const outTokenPre = IERC20.at(tokenOut);29 const preBal: Uint256 = outTokenPre.balanceOf(address.self);3031 // Sort tokens for PoolKey (currency0 < currency1)32 const token0: Address = tokenIn < tokenOut ? tokenIn : tokenOut;33 const token1: Address = tokenIn < tokenOut ? tokenOut : tokenIn;3435 // ── Phase 1: starting price limit ──36 // zeroForOne steps the limit DOWN from the highest price; oneForZero steps it UP from the lowest.37 const firstPool: Tuple = pools[0];38 // slot0 is declared single-output (sqrtPriceX96) in the ABI, so it returns a39 // scalar — the VM can't index a multi-return call result.40 let priceLimit: Uint256 = IUniswapV3Pool.at(firstPool[1]).slot0();41 for (let i = 0; i < pools.length; i = i + 1) {42 const dp: Tuple = pools[i];43 const price: Uint256 = IUniswapV3Pool.at(dp[1]).slot0();44 if (zeroForOne === 1) {45 if (price > priceLimit) { priceLimit = price; }46 } else {47 if (price < priceLimit) { priceLimit = price; }48 }49 }5051 // ── Phase 2: Adaptive price-stepping loop ──52 // minStep = stepSize / 100 — prevents stalling when remaining is small53 let minStep: Uint256 = stepSize / 100;54 if (minStep === 0) {55 minStep = 1;56 }5758 let remaining: Uint256 = amountIn;5960 while (remaining > 0) {61 // Proportional step: shrinks as remaining shrinks62 const step: Uint256 = (stepSize * remaining) / amountIn + minStep;63 if (zeroForOne === 1) {64 priceLimit = priceLimit - step;65 } else {66 priceLimit = priceLimit + step;67 }6869 for (let i = 0; i < pools.length; i = i + 1) {70 const pool: Tuple = pools[i];7172 // Re-read current price (may have moved from prior swaps)73 const currentPrice: Uint256 = IUniswapV3Pool.at(pool[1]).slot0();7475 // Fee-adjusted limit + direction-aware swap condition76 const halfFee: Uint256 = pool[2] / 2;77 let adjustedLimit: Uint256 = 0;78 let doSwap: Uint256 = 0;79 if (zeroForOne === 1) {80 // floor: high-fee pools need a BETTER (higher) price to qualify81 adjustedLimit = (priceLimit * (1000000 + halfFee)) / 1000000;82 if (currentPrice >= adjustedLimit) { doSwap = 1; }83 } else {84 // ceiling: high-fee pools get a LOWER ceiling so they qualify less85 adjustedLimit = (priceLimit * (1000000 - halfFee)) / 1000000;86 if (currentPrice <= adjustedLimit) { doSwap = 1; }87 }8889 if (remaining > 0 && doSwap === 1) {90 // The compiler maps struct keys to the ABI tuple by ALPHABETICAL key91 // sort, positionally (NOT by name) — see best-pool-swap. Keys are92 // ordered so the sort matches ISauceRouter.swap's param order:93 // a=poolType b=pool c=poolKey d=tokenIn e=tokenOut94 // f=amountSpecified g=sqrtPriceLimitX96 h=payer i=recipient95 router.swap({96 a: pool[0],97 b: pool[1],98 c: {99 // poolKey: a=currency0 b=currency1 c=fee d=tickSpacing e=hooks100 a: token0,101 b: token1,102 c: pool[2],103 d: pool[3],104 e: pool[4],105 },106 d: tokenIn,107 e: tokenOut,108 f: remaining,109 g: adjustedLimit,110 h: address.self,111 i: address.self,112 });113114 // Update remaining from actual balance115 remaining = token.balanceOf(address.self);116 }117 }118119 // Re-read remaining after full pool sweep120 remaining = token.balanceOf(address.self);121 }122123 // ── Deposit ONLY the swapped output (delta) into Aave on behalf of beneficiary ──124 const outToken = IERC20.at(tokenOut);125 const postBal: Uint256 = outToken.balanceOf(address.self);126 const swapDelta: Uint256 = postBal - preBal;127128 outToken.approve(aavePool, swapDelta);129 IAavePool.at(aavePool).supply(tokenOut, swapDelta, beneficiary, 0);130131 return swapDelta;132}PLACEHOLDER — Swap & Supply proves a single Sauce program can swap across multiple Base pools and deposit the result into Aave V3 atomically. Pool selection and slippage parameters are discovered off-chain and compiled in; the swap and supply run together in one transaction.
const outA = quoter.quoteExactInputSingle({...});const outB = quoter.quoteExactInputSingle({...});// Branch at inclusion on live quotesconst pool = outA > outB ? poolA : poolB;return router.exactInputSingle({...});Quote every venue inside the transaction and route through the winner. Decision is made at inclusion against the live pool state.
const legs = split(amount, pools);const out = await Promise.all( legs.map(l => swap(in, out, l.amt, l.pool)));require(sum(out) >= minOut, "min not met");return sum(out);Atomically split an order across N pools, execute in parallel, and recombine against a floor. Bigger sizes, smaller per-leg impact.
@contractexport class ERC20 { balanceOf = new Map<address, uint256>(); transfer(to: address, amt: uint256) { this.balanceOf.sub(msg.sender, amt);Same ERC-20 spec in 11 lines of TypeScript vs 28 lines of Solidity. Identical selectors, identical gas, identical audit surface.