.claude/skills/balancer-v3-router/SKILL.md
This skill should be used when the user asks about "Balancer Router", "IRouter", "RouterHooks", "swapSingleTokenExactIn", "addLiquidityProportional", "removeLiquidityProportional", "Permit2 Balancer", "wethIsEth", or needs to understand how users interact with Balancer V3 via routers.
npx skillsauth add cyotee/crane Balancer V3 RouterInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The Router is the user-facing entry point for all Balancer V3 operations. It handles ETH wrapping, Permit2, and the unlock callback pattern.
┌─────────────────────────────────────────────────────────────────┐
│ USER │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ ROUTER │ │
│ │ • ETH handling (wrap/unwrap WETH) │ │
│ │ • Permit2 integration │ │
│ │ • Deadline checks │ │
│ │ • User-friendly interfaces │ │
│ └────────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ VAULT │ │
│ │ • unlock() callback │ │
│ │ • Core swap/liquidity logic │ │
│ │ • Token accounting │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
contract Router is IRouter, RouterHooks {
constructor(
IVault vault,
IWETH weth,
IPermit2 permit2,
string memory routerVersion
) RouterHooks(vault, weth, permit2, routerVersion);
}
function swapSingleTokenExactIn(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountIn,
uint256 minAmountOut,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable returns (uint256 amountOut);
function swapSingleTokenExactOut(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountOut,
uint256 maxAmountIn,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable returns (uint256 amountIn);
// Preview swap without executing
function querySwapSingleTokenExactIn(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountIn,
address sender,
bytes memory userData
) external returns (uint256 amountCalculated);
function querySwapSingleTokenExactOut(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountOut,
address sender,
bytes memory userData
) external returns (uint256 amountCalculated);
function addLiquidityProportional(
address pool,
uint256[] memory maxAmountsIn,
uint256 exactBptAmountOut,
bool wethIsEth,
bytes memory userData
) external payable returns (uint256[] memory amountsIn);
function addLiquidityUnbalanced(
address pool,
uint256[] memory exactAmountsIn,
uint256 minBptAmountOut,
bool wethIsEth,
bytes memory userData
) external payable returns (uint256 bptAmountOut);
function addLiquiditySingleTokenExactOut(
address pool,
IERC20 tokenIn,
uint256 maxAmountIn,
uint256 exactBptAmountOut,
bool wethIsEth,
bytes memory userData
) external payable returns (uint256 amountIn);
function donate(
address pool,
uint256[] memory amountsIn,
bool wethIsEth,
bytes memory userData
) external payable;
function addLiquidityCustom(
address pool,
uint256[] memory maxAmountsIn,
uint256 minBptAmountOut,
bool wethIsEth,
bytes memory userData
) external payable returns (uint256[] memory amountsIn, uint256 bptAmountOut, bytes memory returnData);
function removeLiquidityProportional(
address pool,
uint256 exactBptAmountIn,
uint256[] memory minAmountsOut,
bool wethIsEth,
bytes memory userData
) external payable returns (uint256[] memory amountsOut);
function removeLiquiditySingleTokenExactIn(
address pool,
uint256 exactBptAmountIn,
IERC20 tokenOut,
uint256 minAmountOut,
bool wethIsEth,
bytes memory userData
) external payable returns (uint256 amountOut);
function removeLiquiditySingleTokenExactOut(
address pool,
uint256 maxBptAmountIn,
IERC20 tokenOut,
uint256 exactAmountOut,
bool wethIsEth,
bytes memory userData
) external payable returns (uint256 bptAmountIn);
// Works even when pool is paused
function removeLiquidityRecovery(
address pool,
uint256 exactBptAmountIn,
uint256[] memory minAmountsOut
) external payable returns (uint256[] memory amountsOut);
function initialize(
address pool,
IERC20[] memory tokens,
uint256[] memory exactAmountsIn,
uint256 minBptAmountOut,
bool wethIsEth,
bytes memory userData
) external payable returns (uint256 bptAmountOut);
All operations use the unlock pattern:
// Example: swapSingleTokenExactIn internal flow
function swapSingleTokenExactIn(...) external payable saveSender(msg.sender) returns (uint256) {
return abi.decode(
_vault.unlock(
abi.encodeCall(
RouterHooks.swapSingleTokenHook,
SwapSingleTokenHookParams({
sender: msg.sender,
kind: SwapKind.EXACT_IN,
pool: pool,
tokenIn: tokenIn,
tokenOut: tokenOut,
amountGiven: exactAmountIn,
limit: minAmountOut,
deadline: deadline,
wethIsEth: wethIsEth,
userData: userData
})
)
),
(uint256)
);
}
Implements the actual logic called by the Vault:
abstract contract RouterHooks is RouterCommon {
// Called by Vault during unlock
function swapSingleTokenHook(SwapSingleTokenHookParams calldata params) external returns (uint256);
function addLiquidityHook(AddLiquidityHookParams calldata params) external returns (...);
function removeLiquidityHook(RemoveLiquidityHookParams calldata params) external returns (...);
function initializeHook(InitializeHookParams calldata params) external returns (uint256);
}
// When wethIsEth = true:
// - For input: Router wraps ETH to WETH
// - For output: Router unwraps WETH to ETH
// Receive ETH (used for unwrapping)
receive() external payable {
// Only accept from WETH contract
}
// Return unused ETH
function _returnEth(address sender) internal {
uint256 balance = address(this).balance;
if (balance > 0) {
payable(sender).sendValue(balance);
}
}
// Take tokens from user via Permit2
function _takeTokenIn(
address sender,
IERC20 token,
uint256 amountIn,
bool wethIsEth
) internal {
if (wethIsEth && address(token) == address(_weth)) {
_wrapEth(sender, amountIn);
} else {
permit2.transferFrom(sender, address(_vault), uint160(amountIn), address(token));
}
}
// Stores msg.sender in transient storage for hook callbacks
modifier saveSender(address sender) {
_currentSwapTokensInSlot().tstore(0);
_currentSwapTokensOutSlot().tstore(0);
_currentSwapTokenInAmounts().tstore(0, 0);
_currentSwapTokenOutAmounts().tstore(0, 0);
_sender().tstore(sender);
_;
}
For multi-hop swaps and batch operations:
contract BatchRouter is IBatchRouter, BatchRouterHooks {
// Multi-hop swaps
function swapExactIn(
SwapPathExactAmountIn[] memory paths,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable returns (uint256[] memory pathAmountsOut, ...);
function swapExactOut(
SwapPathExactAmountOut[] memory paths,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable returns (uint256[] memory pathAmountsIn, ...);
}
For nested pool operations (pools containing BPT):
contract CompositeLiquidityRouter is ICompositeLiquidityRouter, CompositeLiquidityRouterHooks {
// Add liquidity to nested pools in one transaction
// Remove liquidity from nested pools in one transaction
}
For ERC4626 buffer operations:
contract BufferRouter is IBufferRouter {
// Initialize buffers
// Add/remove buffer liquidity
// Direct wrap/unwrap through buffers
}
pkg/vault/contracts/Router.sol - Main Router implementationpkg/vault/contracts/RouterHooks.sol - Hook implementationspkg/vault/contracts/RouterCommon.sol - Shared utilitiespkg/vault/contracts/BatchRouter.sol - Multi-hop operationspkg/vault/contracts/CompositeLiquidityRouter.sol - Nested poolsdevelopment
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
documentation
Write to contracts and send transactions. Use when executing state-changing contract functions.
development
HTTP and WebSocket transports for blockchain connectivity. Use when configuring network connections.
data-ai
Read contract data with type-safe ABI. Use when querying smart contract view/pure functions.