Minimizing Cost When Moving Assets Across Chains
Bridging assets between blockchains is rarely a single, clean transaction. Every bridge route is backed by a liquidity pool with finite depth, and every unit of value you push through that pool causes price impact (slippage) on top of the base fee. If you naively send your entire transfer through a single “cheapest-looking” bridge, you can end up paying far more than necessary — especially for large transfers. The smarter approach is to split the transfer across multiple routes so that the marginal cost of each route stays balanced. This is directly analogous to the classic water-filling problem in information theory and portfolio allocation.
In this article we build a concrete, numerical example: moving a stablecoin-pegged asset from Ethereum to BSC through three independent bridge routes (via Arbitrum, Optimism, and Polygon aggregator liquidity), each modeled as a constant-product AMM pool with its own reserves and fee. We’ll derive the optimal allocation analytically, implement a fast closed-form solver, benchmark it against brute-force grid search, and visualize the results in both 2D and 3D.
1. The Model
Each bridge route $i$ is modeled as a constant-product AMM pool with input reserve $R_{in,i}$, output reserve $R_{out,i}$, and fee rate $f_i$. If we send amount $a$ through route $i$, the effective input after fees is:
$$a_{\text{eff}} = a(1-f_i)$$
and the output received is:
$$\Delta y_i(a) = \frac{R_{out,i} \cdot a_{\text{eff}}}{R_{in,i} + a_{\text{eff}}}$$
The cost of routing amount $a$ through route $i$ (in input-equivalent units) is:
$$C_i(a) = a - \Delta y_i(a)$$
Given a total transfer amount $A$ that we want to split across $n$ routes as $(a_1, a_2, \dots, a_n)$, the optimization problem is:
$$\min_{a_1,\dots,a_n} \sum_{i=1}^{n} C_i(a_i) \quad \text{s.t.} \quad \sum_{i=1}^{n} a_i = A, \quad a_i \ge 0$$
Since $\Delta y_i(a)$ is concave and increasing (diminishing returns from slippage), this is a convex optimization problem. The Karush-Kuhn-Tucker (KKT) condition tells us that at the optimum, the marginal price (marginal output per marginal input) must be equal across all active routes:
$$\frac{d(\Delta y_i)}{da}\Big|_{a_i^*} = \lambda \quad \text{for all active } i$$
where $\lambda$ is a shared shadow price. This is the classic water-filling condition. The marginal price function for a constant-product pool is:
$$p_i(a) = \frac{d(\Delta y_i)}{da} = \frac{R_{out,i}(1-f_i)R_{in,i}}{\left(R_{in,i} + a(1-f_i)\right)^2}$$
This is monotonically decreasing in $a$, which lets us invert it in closed form to get $a_i(\lambda)$, and then find the $\lambda$ that satisfies $\sum_i a_i(\lambda) = A$ using bisection — no iterative numerical optimizer needed. This closed-form + bisection approach is dramatically faster than a generic nonlinear solver or grid search, which matters if you want to re-optimize routing on every block.
2. Full Source Code
Run this single cell in Google Colaboratory.
1 | import numpy as np |
3. Code Walkthrough
Section 1 — Route definitions. We model three bridge routes from Ethereum to BSC, each as a tuple of (Rin, Rout, fee) representing the aggregate liquidity depth exposed by that route (a bridge aggregator abstraction). Route A and B have shallower liquidity and thus more slippage per unit of size; Route C has the deepest liquidity but the lowest raw marginal price at zero.
Section 2 — Core AMM math. amm_output implements the standard constant-product formula $\Delta y = \frac{R_{out} \cdot a(1-f)}{R_{in} + a(1-f)}$. marginal_price is its analytic derivative — the instantaneous exchange rate you get for an infinitesimally small additional unit sent through the pool. inverse_marginal algebraically inverts this derivative, letting us ask “what input amount gives this route a marginal price of exactly $\lambda$?” in closed form, with no iterative root-finding required for a single route.
Section 3 — Water-filling solver. water_filling performs bisection search over the shared shadow price $\lambda$. For each candidate $\lambda$, total_alloc sums up how much each route would receive under that price using the closed-form inverse. Because total_alloc is monotonically decreasing in $\lambda$, standard bisection converges quickly (100 iterations is far more than enough for double-precision accuracy). Once $\lambda^*$ is found, allocations are rescaled slightly to make sure they sum to exactly $A$ despite floating-point rounding.
Section 4 — Baselines. naive_best_single evaluates sending the entire amount through each individual route and picks the cheapest one — this mirrors how many simple bridge UIs behave today. equal_split_cost is a naive “just split evenly across routes” heuristic, useful to show that even simple splitting isn’t as good as marginal-price-aware splitting.
Section 5 — Brute-force grid search. This is the “slow” reference implementation, included purely for benchmarking. It evaluates the cost function on a dense 400×400 grid over $(a_1, a_2)$ (with $a_3$ implied), fully vectorized with NumPy. Even vectorized, it is orders of magnitude slower and less precise than the closed-form water-filling approach, because its accuracy is capped by grid resolution.
Section 6 — Speed benchmark. We directly time both methods on the same $A = 1{,}000{,}000$ transfer and print the wall-clock time and resulting cost for each. This demonstrates concretely why the closed-form approach is the one to use for time-sensitive scenarios like on-chain routing or real-time UI quotes: it’s both faster and more accurate.
Section 7 — Sweep over transfer size. We compute the cost (in basis points) of all three strategies across a range of transfer amounts from 10,000 up to 2,000,000, along with the percentage savings of water-filling over the naive best-single-route strategy.
Sections 8–10 — Visualizations. These build the topology diagram, the 2D cost/savings charts, and the 3D cost surface, described in detail below.
4. Visualizing the Results
4.1 Bridge Route Topology
The first chart is a simple schematic of the three parallel liquidity routes between Ethereum and BSC, annotated with each route’s reserves and fee. It’s a useful mental model before diving into the numbers: think of these as three separate pipes of different diameters (liquidity depth) between the two chains.

4.2 Cost vs Transfer Size, and Savings from Splitting
The left panel plots routing cost in basis points against transfer size for all three strategies. Notice how the “best single route” (red) and “equal split” (orange, dashed) curves rise steeply as the transfer size grows — because pushing a large amount through one pool (or splitting it blindly in half/thirds) causes disproportionate slippage. The water-filling curve (green) stays consistently lower because it dynamically shifts more volume toward whichever route currently has the best marginal price, and stops adding to a route the moment its marginal price drops below what another route still offers.
The right panel converts this into a direct savings percentage: for small transfers, all three routes have similar marginal prices at low volume, so the benefit of splitting is small. As the transfer size grows, the gap widens substantially, since a single pool alone would suffer heavy slippage that splitting avoids entirely.

4.3 The 3D Cost Surface
The 3D surface shows total cost as a function of how much is sent through Route A ($a_1$) and Route B ($a_2$), with Route C absorbing the remainder ($a_3 = A - a_1 - a_2$) for a fixed total transfer of 1,000,000 units. The bowl-like shape confirms convexity: cost rises sharply near the edges of the triangular domain (where one route is forced to absorb almost everything, causing heavy slippage) and dips toward a minimum somewhere in the interior. The red marker shows the exact point found by the water-filling algorithm — sitting precisely at the bottom of the bowl, confirming that the closed-form solution matches the true numerical minimum.

4.4 Benchmark Output
The printed benchmark output (from Section 6 of the code) shows the wall-clock time for the closed-form water-filling method versus the brute-force grid search, along with the resulting allocation across the three routes for a 1,000,000-unit transfer.
=== Speed & Accuracy Benchmark (A = 1,000,000) === Water-filling (closed-form): 0.7574 ms | cost = 387,863.9679 Brute-force grid (400x400): 9.3112 ms | cost = 387,864.1008 Speedup: 12.3x faster, with lower (more accurate) cost Route A (via Arbitrum) -> allocate 312,904.33 (31.29%) Route B (via Optimism) -> allocate 187,889.00 (18.79%) Route C (via Polygon) -> allocate 499,206.67 (49.92%)
5. Takeaways
The core insight is that cross-chain routing cost isn’t just about comparing headline fees — it’s a convex optimization problem driven by AMM slippage curves. The optimal strategy equalizes marginal price across all active routes rather than chasing the lowest average price of a single route, and for constant-product pools this equalization condition has a closed-form solution that can be solved via simple bisection instead of a general-purpose optimizer. This makes it practical to re-solve the allocation in real time — for example inside a bridge aggregator’s quoting engine — every time liquidity conditions or the requested transfer size change.






























