recipient.
The program checks the total WETH received against minTokens. It also checks the average price when maxAvgPrice is nonzero. Pools are not sorted by price, so this example does not promise the best available execution.
Target chains: Base. The program requires Aerodrome Slipstream’s factories. Compiling for Ethereum fails (`AerodromeSlipstream.CLFactory` is not deployed on ethereum.).
Prerequisites
Install the Sauce CLI to compile and simulate. To send the transaction, configure a wallet with funds for gas and the Kitchen fee, and provide USDC in your Pot once it is deployed, or through--fund.
Program
pot is the Pot the program runs on, available without declaring it (Names).
distributed-swap.sauce.ts
How it works
- No amount is split in advance. Each pool is offered
pot.USDC - kept, all of the budget still unspent, and its own limit stops it; the Router pays a pool only what it asks for, so the rest stays in the Pot for the next pool. The loops only look pools up; a V4 pool is its id,crypto.keccak256(abi.encode(...))of its key, aUint256. The two V4 keys are theUniswap V4 key …rowssauce poolslists with liquidity; it lists the keys 100:1 and 10000:200 as “no liquidity at its price”, and a pool with no liquidity is skipped. - Pools fill in the loops’ order, not in order of price. Each pool in turn trades up to its own limit, so a pool early in the loops can spend the budget at prices up to
priceLimitwhile a cheaper pool later in them is offered nothing, and a looserpriceLimitcan buy less. On Base at block 52997580, 10,000 USDC bought 4.172728624863845599 WETH at a 2,397 limit and 4.172225574547365654 WETH at 2,420. At 2,420 theCLFactory0.015% pool, fourth in the first loop at 2,397.10 USDC per WETH with its fee, took 9,690.865139 USDC, and the Uniswap V3 0.05% and PancakeSwap V3 0.01% pools, at 2,396.34 and 2,396.26, were offered nothing. ApriceLimitclose to the pricessauce poolslists keeps every pool near them. Putting the loops in the order of those prices would not make the fill follow price: the first pool still trades up topriceLimitbefore the next is offered anything, and the order is fixed in the program while prices move every block. - Each pool’s limit is the caller’s sqrt price moved by √(1 − fee), with the fee read from the pool in the cook: a Slipstream pool’s fee can change between blocks, and a V4 pool’s is its LP fee combined with the direction’s protocol fee. The last unit each pool fills costs
priceLimitwith its fee paid.tradeskips a missing pool, an empty one and one already at or past its own limit, which would otherwise revert its swap. - A PancakeSwap V3 pool is read through
PancakeswapV3.Pool, whoseslot0declaresfeeProtocolas auint32. A read decodes and range-checks every declared output, so the same read throughUniswapV3.Pool(auint8there) reverts: on the fork the 0.01% pool’sfeeProtocolwas 216272100. boughtis the change inrecipient.WETHandspentthe change in the Pot’s USDC.minTokensis checked on those totals after the loops, and so is the average whenmaxAvgPriceis not 0.minTokensatbudget * 1e18 / priceLimit, rounded up, is the least a full fill at the limit would buy. It does not make the order fill-or-kill: pools that fill below the limit can reach it with part of the budget unspent, and that part is refunded. Only the unspent budget goes torecipient, not the Pot’s whole USDC balance.- To spend all of the budget or nothing, add
require(unspent == 0, "budget not filled");before the refund. The program as shown leaves it out, so that aminTokensof 0 buys what fills withinpriceLimitand returns the rest.
Run the example
1
Read the pools and set the limit
sauce pools lists each pool’s price per WETH with its fee. Its Uniswap V4 key … rows give the keys with liquidity, which the program’s key loop lists. sauce price turns a price in USDC per WETH into the program’s units: its “Buying WETH” block gives priceLimit 2333000000 for 2,333 USDC.2
Compile
3
Simulate
maxAvgPrice at 0 soon after reading the pools. Read the average from main returns as spent * 1e18 / bought, set maxAvgPrice halfway from it to priceLimit, rounded up, and simulate again with that value.4
Send
sauce tx prints the unsigned transaction instead, for your own signer, with the same funding and arguments:A
priceLimit at or below every pool’s price with its fee buys nothing and sends the whole budget back to recipient without reverting. Set minTokens above 0 so that case reverts.Simulation result
Simulated on a local fork of Base at block 53122981 with the current contracts. Nothing was signed or sent; values change with the block. The run is the Simulate step’s command withmaxAvgPrice 2332489272. On the fork sauce pools listed 24 pools of the pair with liquidity, the V4 keys 500:10 and 3000:60 among them; the cheapest with their fee were a Slipstream pool at 2,317.58 USDC per WETH and a Uniswap V3 pool at 2,330.92.
CLFactory pools of the first loop, the last the 0.015% pool (0xb2cc…DC59), which took 9,980.927101 USDC. The pools after it, the two V4 pools included, were looked up but not read, because no budget was left. The recipient received 4.288204120713655838 WETH for 10,000 USDC, an average of 2,331.978543 USDC per WETH. The same run with maxAvgPrice 0 returned the same three values; 2332489272 is halfway from that average to priceLimit, rounded up.
On the same fork, with maxAvgPrice at 2,331.00 (2331000000), below that average, the cook reverts:
Next steps
- Try-fallback swap: one pool, and a second one when the first fails, in an atomic
tryblock. - Buy tokenized stock: one pool chosen by price, on Ethereum.
- Solana distributed swap: the same order over Solana’s pools.
sauce pools,sauce price,sauce simulate, Tokens
