From 98f870e7bd30e391146e35db319f05876ed1ef47 Mon Sep 17 00:00:00 2001 From: Cardinal Date: Wed, 8 Apr 2026 15:30:58 +0200 Subject: [PATCH 1/2] refactor(router): apply security/gas fixes, redeploy to 0xf244cC25EAD03a99de8B407A3237aaf54D1b779C - Replace stray require() with custom error in uniswapV3SwapCallback - Eliminate two token0()/token1() external calls in callback (derive from path) - Emit actual amountIn (not maxAmountIn) in all BatchCreated/ToppedUp events - Add zero-address guard in constructor - Trim IWXDAI and ISushiV3Pool interfaces to only used methods - Replace byte-by-byte _skipToken loop with assembly mstore copy - Update deployment artifacts and app constant to new address --- contracts/SushiSwapStampsRouter.sol | 47 +++++++++++-------- deployments/gnosis/SushiSwapStampsRouter.json | 26 +++++----- src/app/components/constants.ts | 2 +- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/contracts/SushiSwapStampsRouter.sol b/contracts/SushiSwapStampsRouter.sol index d462492..1ccb130 100644 --- a/contracts/SushiSwapStampsRouter.sol +++ b/contracts/SushiSwapStampsRouter.sol @@ -58,10 +58,6 @@ interface IERC20 { interface IWXDAI { function deposit() external payable; function withdraw(uint256 amount) external; - function approve(address spender, uint256 amount) external returns (bool); - function transfer(address recipient, uint256 amount) external returns (bool); - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); - function balanceOf(address account) external view returns (uint256); } interface ISushiV3Pool { @@ -75,7 +71,6 @@ interface ISushiV3Pool { function token0() external view returns (address); function token1() external view returns (address); - function fee() external view returns (uint24); } interface ISushiV3Factory { @@ -212,6 +207,7 @@ contract SushiSwapStampsRouter { // ─── Constructor ────────────────────────────────────────────────────────── constructor(address _stampsRegistry) { + require(_stampsRegistry != address(0), "zero registry"); stampsRegistry = IStampsRegistry(_stampsRegistry); } @@ -275,10 +271,12 @@ contract SushiSwapStampsRouter { uint256 bzzAmountOut, CreateBatchParams calldata p ) external { + address tokenIn = _lastToken(path); + uint256 balBefore = IERC20(tokenIn).balanceOf(msg.sender); _swapExactOutput(path, msg.sender, maxAmountIn, bzzAmountOut); + uint256 actualAmountIn = balBefore - IERC20(tokenIn).balanceOf(msg.sender); bytes32 batchId = _approveBzzAndCreate(p, bzzAmountOut); - address tokenIn = _lastToken(path); - emit BatchCreatedViaSwap(batchId, p.owner, tokenIn, maxAmountIn, bzzAmountOut); + emit BatchCreatedViaSwap(batchId, p.owner, tokenIn, actualAmountIn, bzzAmountOut); } /** @@ -299,8 +297,9 @@ contract SushiSwapStampsRouter { if (msg.value < maxAmountIn) revert InsufficientNativeValue(); IWXDAI(WXDAI).deposit{value: maxAmountIn}(); _swapExactOutput(path, address(this), maxAmountIn, bzzAmountOut); + uint256 actualAmountIn = maxAmountIn - IERC20(WXDAI).balanceOf(address(this)); bytes32 batchId = _approveBzzAndCreate(p, bzzAmountOut); - emit BatchCreatedViaSwap(batchId, p.owner, address(0), maxAmountIn, bzzAmountOut); + emit BatchCreatedViaSwap(batchId, p.owner, address(0), actualAmountIn, bzzAmountOut); _refundNative(); } @@ -322,10 +321,12 @@ contract SushiSwapStampsRouter { bytes32 batchId, uint256 topupAmountPerChunk ) external { + address tokenIn = _lastToken(path); + uint256 balBefore = IERC20(tokenIn).balanceOf(msg.sender); _swapExactOutput(path, msg.sender, maxAmountIn, bzzAmountOut); + uint256 actualAmountIn = balBefore - IERC20(tokenIn).balanceOf(msg.sender); _approveBzzAndTopUp(batchId, topupAmountPerChunk, bzzAmountOut); - address tokenIn = _lastToken(path); - emit BatchToppedUpViaSwap(batchId, tokenIn, maxAmountIn, bzzAmountOut); + emit BatchToppedUpViaSwap(batchId, tokenIn, actualAmountIn, bzzAmountOut); } /** @@ -347,8 +348,9 @@ contract SushiSwapStampsRouter { if (msg.value < maxAmountIn) revert InsufficientNativeValue(); IWXDAI(WXDAI).deposit{value: maxAmountIn}(); _swapExactOutput(path, address(this), maxAmountIn, bzzAmountOut); + uint256 actualAmountIn = maxAmountIn - IERC20(WXDAI).balanceOf(address(this)); _approveBzzAndTopUp(batchId, topupAmountPerChunk, bzzAmountOut); - emit BatchToppedUpViaSwap(batchId, address(0), maxAmountIn, bzzAmountOut); + emit BatchToppedUpViaSwap(batchId, address(0), actualAmountIn, bzzAmountOut); _refundNative(); } @@ -365,7 +367,7 @@ contract SushiSwapStampsRouter { int256 amount1Delta, bytes calldata data ) external { - require(amount0Delta > 0 || amount1Delta > 0, "Zero deltas"); + if (amount0Delta <= 0 && amount1Delta <= 0) revert InvalidCallback(); SwapCallbackData memory cb = abi.decode(data, (SwapCallbackData)); @@ -374,11 +376,11 @@ contract SushiSwapStampsRouter { address expectedPool = ISushiV3Factory(SUSHI_FACTORY).getPool(tokenOut, tokenIn, fee); if (msg.sender != expectedPool) revert InvalidCallback(); - // Determine which token we owe to the calling pool and how much. - // The positive delta is what the pool expects us to pay. - (address tokenOwed, uint256 amountOwed) = amount0Delta > 0 - ? (ISushiV3Pool(msg.sender).token0(), uint256(amount0Delta)) - : (ISushiV3Pool(msg.sender).token1(), uint256(amount1Delta)); + // tokenIn is always what we owe: V3 pools sort tokens by address, so + // tokenIn is token0 iff tokenIn < tokenOut (zeroForOne=true → amount0Delta > 0). + // Either way the positive delta corresponds to tokenIn. + address tokenOwed = tokenIn; + uint256 amountOwed = uint256(amount0Delta > 0 ? amount0Delta : amount1Delta); if (_hasMultiplePools(cb.path)) { // Multi-hop: continue to next pool. Skip the first token from path to get @@ -548,9 +550,14 @@ contract SushiSwapStampsRouter { function _skipToken(bytes memory path) internal pure returns (bytes memory skipped) { uint256 newLen = path.length - NEXT_OFFSET; skipped = new bytes(newLen); - // Copy from offset NEXT_OFFSET onward - for (uint256 i = 0; i < newLen; i++) { - skipped[i] = path[i + NEXT_OFFSET]; + assembly { + let src := add(add(path, 0x20), NEXT_OFFSET) + let dst := add(skipped, 0x20) + // Copy 32 bytes at a time; the last chunk may write up to 31 bytes past + // newLen, but into the 32-byte-aligned padding that `new bytes` allocates. + for { let i := 0 } lt(i, newLen) { i := add(i, 32) } { + mstore(add(dst, i), mload(add(src, i))) + } } } diff --git a/deployments/gnosis/SushiSwapStampsRouter.json b/deployments/gnosis/SushiSwapStampsRouter.json index 25a72c1..61b7cc7 100644 --- a/deployments/gnosis/SushiSwapStampsRouter.json +++ b/deployments/gnosis/SushiSwapStampsRouter.json @@ -1,5 +1,5 @@ { - "address": "0x2a0a54368Bb6b0D8fa31568D092ffBDf350ab553", + "address": "0xf244cC25EAD03a99de8B407A3237aaf54D1b779C", "abi": [ { "inputs": [ @@ -473,19 +473,19 @@ "type": "receive" } ], - "transactionHash": "0x1e89af5249b93f7491b74e4e7c88398c21738fc2abced8adf39f501def851115", + "transactionHash": "0x55c5a6b12fd15d009a5e6cb355d3fa2bb0d3fa5587ebdf58a02aea2f7876bdc9", "receipt": { "to": null, "from": "0xb1C7F17Ed88189Abf269Bf68A3B2Ed83C5276aAe", - "contractAddress": "0x2a0a54368Bb6b0D8fa31568D092ffBDf350ab553", - "transactionIndex": 14, - "gasUsed": "1396096", + "contractAddress": "0xf244cC25EAD03a99de8B407A3237aaf54D1b779C", + "transactionIndex": 38, + "gasUsed": "1451931", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x96eb23ebc9b335635f99f29cb9e81f05a1a816f46b82fcdd09f618e8860e819a", - "transactionHash": "0x1e89af5249b93f7491b74e4e7c88398c21738fc2abced8adf39f501def851115", + "blockHash": "0x378c3f92b4148a4cf93fb0e3902d98f19da23d591c8145dcdd2689d54e7a6093", + "transactionHash": "0x55c5a6b12fd15d009a5e6cb355d3fa2bb0d3fa5587ebdf58a02aea2f7876bdc9", "logs": [], - "blockNumber": 45555563, - "cumulativeGasUsed": "8074489", + "blockNumber": 45569163, + "cumulativeGasUsed": "11951888", "status": 1, "byzantium": true }, @@ -493,10 +493,10 @@ "0x5EBfBeFB1E88391eFb022d5d33302f50a46bF4f3" ], "numDeployments": 1, - "solcInputHash": "90fe721cd304da2d010b5da7732aee0a", - "metadata": "{\"compiler\":{\"version\":\"0.8.23+commit.f704f362\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_stampsRegistry\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"BzzApproveFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BzzTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientNativeValue\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCallback\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPath\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NativeRefundFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PoolNotFound\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"required\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maximum\",\"type\":\"uint256\"}],\"name\":\"SlippageExceeded\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"bzzAmount\",\"type\":\"uint256\"}],\"name\":\"BatchCreatedViaSwap\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"bzzAmount\",\"type\":\"uint256\"}],\"name\":\"BatchToppedUpViaSwap\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BZZ\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SUSHI_FACTORY\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SUSHI_QUOTER\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"WXDAI\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nodeAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"initialBalancePerChunk\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"depth\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"bucketDepth\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"immutable_\",\"type\":\"bool\"}],\"internalType\":\"struct SushiSwapStampsRouter.CreateBatchParams\",\"name\":\"p\",\"type\":\"tuple\"}],\"name\":\"createBatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nodeAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"initialBalancePerChunk\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"depth\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"bucketDepth\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"immutable_\",\"type\":\"bool\"}],\"internalType\":\"struct SushiSwapStampsRouter.CreateBatchParams\",\"name\":\"p\",\"type\":\"tuple\"}],\"name\":\"createBatchNative\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"}],\"name\":\"quoteMultiHop\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"}],\"name\":\"quoteSingleHop\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stampsRegistry\",\"outputs\":[{\"internalType\":\"contract IStampsRegistry\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"topupAmountPerChunk\",\"type\":\"uint256\"}],\"name\":\"topUp\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"topupAmountPerChunk\",\"type\":\"uint256\"}],\"name\":\"topUpNative\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"amount0Delta\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1Delta\",\"type\":\"int256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"uniswapV3SwapCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"createBatch(bytes,uint256,uint256,(address,address,uint256,uint8,uint8,bytes32,bool))\":{\"details\":\"`tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\",\"params\":{\"bzzAmountOut\":\"Exact BZZ needed (= swarmBatchTotal = initialBalancePerChunk \\u00d7 2^depth)\",\"maxAmountIn\":\"Maximum tokenIn to spend (slippage protection)\",\"p\":\"Batch creation parameters\",\"path\":\"Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\"}},\"createBatchNative(bytes,uint256,uint256,(address,address,uint256,uint8,uint8,bytes32,bool))\":{\"details\":\"Send msg.value \\u2265 maxAmountIn. Excess xDAI is refunded.\",\"params\":{\"bzzAmountOut\":\"Exact BZZ needed\",\"maxAmountIn\":\"Maximum xDAI to spend\",\"p\":\"Batch creation parameters\",\"path\":\"Exact-output path where the final token MUST be WXDAI: BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\"}},\"quoteMultiHop(bytes,uint256)\":{\"params\":{\"bzzAmountOut\":\"Exact BZZ amount wanted\",\"path\":\"Exact-output encoded path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\"},\"returns\":{\"amountIn\":\" Input tokens required (before slippage)\"}},\"quoteSingleHop(address,uint24,uint256)\":{\"params\":{\"bzzAmountOut\":\"Exact BZZ amount wanted\",\"fee\":\"Pool fee tier (e.g. 500, 3000, 10000)\",\"tokenIn\":\"Input token (use WXDAI for native xDAI quotes)\"},\"returns\":{\"amountIn\":\" Input tokens required (before slippage)\"}},\"topUp(bytes,uint256,uint256,bytes32,uint256)\":{\"details\":\"`tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\",\"params\":{\"batchId\":\"Batch to top up\",\"bzzAmountOut\":\"Exact BZZ needed (= topupAmountPerChunk \\u00d7 2^depth)\",\"maxAmountIn\":\"Maximum tokenIn to spend\",\"path\":\"Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\",\"topupAmountPerChunk\":\"Per-chunk top-up amount (matches registry call)\"}},\"topUpNative(bytes,uint256,uint256,bytes32,uint256)\":{\"details\":\"Send msg.value \\u2265 maxAmountIn. Excess xDAI is refunded.\",\"params\":{\"batchId\":\"Batch to top up\",\"bzzAmountOut\":\"Exact BZZ needed\",\"maxAmountIn\":\"Maximum xDAI to spend\",\"path\":\"BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\",\"topupAmountPerChunk\":\"Per-chunk top-up amount\"}},\"uniswapV3SwapCallback(int256,int256,bytes)\":{\"details\":\"Implements the Uniswap V3 callback interface (SushiSwap V3 is compatible). For multi-hop swaps, this callback chains into the next pool swap before paying the current pool, routing tokens directly between pools.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"BZZ()\":{\"notice\":\"BZZ token on Gnosis\"},\"SUSHI_FACTORY()\":{\"notice\":\"SushiSwap V3 Factory on Gnosis\"},\"SUSHI_QUOTER()\":{\"notice\":\"SushiSwap V3 QuoterV2 on Gnosis\"},\"WXDAI()\":{\"notice\":\"Wrapped xDAI on Gnosis\"},\"createBatch(bytes,uint256,uint256,(address,address,uint256,uint8,uint8,bytes32,bool))\":{\"notice\":\"Swap `tokenIn` \\u2192 BZZ via the given path and create a Swarm stamp batch.\"},\"createBatchNative(bytes,uint256,uint256,(address,address,uint256,uint8,uint8,bytes32,bool))\":{\"notice\":\"Swap native xDAI \\u2192 BZZ and create a Swarm stamp batch.\"},\"quoteMultiHop(bytes,uint256)\":{\"notice\":\"Quote: how many input tokens are needed to get exactly `bzzAmountOut` BZZ via a multi-hop path.\"},\"quoteSingleHop(address,uint24,uint256)\":{\"notice\":\"Quote: how many `tokenIn` are needed to get exactly `bzzAmountOut` BZZ via a single-hop pool.\"},\"topUp(bytes,uint256,uint256,bytes32,uint256)\":{\"notice\":\"Swap `tokenIn` \\u2192 BZZ and top up an existing Swarm stamp batch.\"},\"topUpNative(bytes,uint256,uint256,bytes32,uint256)\":{\"notice\":\"Swap native xDAI \\u2192 BZZ and top up an existing Swarm stamp batch.\"},\"uniswapV3SwapCallback(int256,int256,bytes)\":{\"notice\":\"Called by a SushiSwap V3 pool during swap execution.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/SushiSwapStampsRouter.sol\":\"SushiSwapStampsRouter\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[],\"viaIR\":true},\"sources\":{\"contracts/SushiSwapStampsRouter.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.23;\\n\\n/*\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2557 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\n \\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u255d\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u255a\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u255a\\u2588\\u2588\\u2588\\u2554\\u2588\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\n \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d\\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\u255a\\u2550\\u255d\\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u255d\\u255a\\u2550\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\u255a\\u2550\\u255d\\n\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u255a\\u2550\\u2550\\u2588\\u2588\\u2554\\u2550\\u2550\\u255d\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2588\\u2588\\u2588\\u2588\\u2554\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\n \\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u255a\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u255a\\u2550\\u255d \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\n \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\u255a\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d\\n\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u255a\\u2550\\u2550\\u2588\\u2588\\u2554\\u2550\\u2550\\u255d\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\n \\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2554\\u2550\\u2550\\u255d \\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u255a\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\u255a\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\n \\u255a\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d\\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\n*/\\n\\n/**\\n * @title SushiSwapStampsRouter\\n * @notice Swaps any Gnosis-chain token to BZZ via SushiSwap V3 and atomically\\n * creates or tops up a Swarm postage-stamp batch in a single transaction.\\n *\\n * @dev Implements the Uniswap V3 callback interface (SushiSwap V3 is fully compatible).\\n * Supports both single-hop and multi-hop exact-output swaps via path encoding.\\n *\\n * Path encoding for exactOutput swaps (reversed token order):\\n * single-hop: BZZ ++ uint24(fee) ++ tokenIn (43 bytes)\\n * two-hop: BZZ ++ uint24(fee2) ++ mid ++ uint24(fee1) ++ tokenIn (66 bytes)\\n *\\n * Quote functions are non-view (Quoter simulates swaps internally) but are\\n * designed to be called via eth_call for gas-free estimation.\\n *\\n * Gnosis-chain addresses (hardcoded):\\n * BZZ = 0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da\\n * WXDAI = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d\\n * Quoter = 0xb1e835dc2785b52265711e17fccb0fd018226a6e (SushiSwap V3 QuoterV2)\\n * Factory= 0xf78031cbca409f2fb6876bdfdbc1b2df24cf9bef (SushiSwap V3 Factory)\\n */\\n\\n// \\u2500\\u2500\\u2500 Interfaces \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\ninterface IERC20 {\\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n function approve(address spender, uint256 amount) external returns (bool);\\n function balanceOf(address account) external view returns (uint256);\\n}\\n\\ninterface IWXDAI {\\n function deposit() external payable;\\n function withdraw(uint256 amount) external;\\n function approve(address spender, uint256 amount) external returns (bool);\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\\n function balanceOf(address account) external view returns (uint256);\\n}\\n\\ninterface ISushiV3Pool {\\n function swap(\\n address recipient,\\n bool zeroForOne,\\n int256 amountSpecified,\\n uint160 sqrtPriceLimitX96,\\n bytes calldata data\\n ) external returns (int256 amount0, int256 amount1);\\n\\n function token0() external view returns (address);\\n function token1() external view returns (address);\\n function fee() external view returns (uint24);\\n}\\n\\ninterface ISushiV3Factory {\\n function getPool(\\n address tokenA,\\n address tokenB,\\n uint24 fee\\n ) external view returns (address pool);\\n}\\n\\ninterface IQuoterV2 {\\n struct QuoteExactOutputSingleParams {\\n address tokenIn;\\n address tokenOut;\\n uint256 amount;\\n uint24 fee;\\n uint160 sqrtPriceLimitX96;\\n }\\n\\n function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params)\\n external\\n returns (\\n uint256 amountIn,\\n uint160 sqrtPriceX96After,\\n uint32 initializedTicksCrossed,\\n uint256 gasEstimate\\n );\\n\\n function quoteExactOutput(bytes memory path, uint256 amountOut)\\n external\\n returns (\\n uint256 amountIn,\\n uint160[] memory sqrtPriceX96AfterList,\\n uint32[] memory initializedTicksCrossedList,\\n uint256 gasEstimate\\n );\\n}\\n\\ninterface IStampsRegistry {\\n function createBatchRegistry(\\n address _owner,\\n address _nodeAddress,\\n uint256 _initialBalancePerChunk,\\n uint8 _depth,\\n uint8 _bucketDepth,\\n bytes32 _nonce,\\n bool _immutable\\n ) external;\\n\\n function topUpBatch(bytes32 _batchId, uint256 _topupAmountPerChunk) external;\\n}\\n\\n// \\u2500\\u2500\\u2500 Router Contract \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\ncontract SushiSwapStampsRouter {\\n\\n // \\u2500\\u2500\\u2500 Constants \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /// @notice BZZ token on Gnosis\\n address public constant BZZ = 0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da;\\n\\n /// @notice Wrapped xDAI on Gnosis\\n address public constant WXDAI = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d;\\n\\n /// @notice SushiSwap V3 QuoterV2 on Gnosis\\n address public constant SUSHI_QUOTER = 0xb1E835Dc2785b52265711e17fCCb0fd018226a6e;\\n\\n /// @notice SushiSwap V3 Factory on Gnosis\\n address public constant SUSHI_FACTORY = 0xf78031CBCA409F2FB6876BDFDBc1b2df24cF9bEf;\\n\\n /// @notice Minimum sqrt price limit (used when selling token0 \\u2192 token1, zeroForOne=true)\\n uint160 internal constant MIN_SQRT_RATIO = 4295128739;\\n\\n /// @notice Maximum sqrt price limit (used when selling token1 \\u2192 token0, zeroForOne=false)\\n uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;\\n\\n // Path encoding offsets (bytes): address=20, fee=3, nextOffset=23, popOffset=43\\n uint256 private constant ADDR_SIZE = 20;\\n uint256 private constant FEE_SIZE = 3;\\n uint256 private constant NEXT_OFFSET = 23; // ADDR_SIZE + FEE_SIZE\\n uint256 private constant POP_OFFSET = 43; // NEXT_OFFSET + ADDR_SIZE\\n\\n // \\u2500\\u2500\\u2500 Immutables \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n IStampsRegistry public immutable stampsRegistry;\\n\\n // \\u2500\\u2500\\u2500 Events \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n event BatchCreatedViaSwap(\\n bytes32 indexed batchId,\\n address indexed owner,\\n address tokenIn,\\n uint256 amountIn,\\n uint256 bzzAmount\\n );\\n\\n event BatchToppedUpViaSwap(\\n bytes32 indexed batchId,\\n address tokenIn,\\n uint256 amountIn,\\n uint256 bzzAmount\\n );\\n\\n // \\u2500\\u2500\\u2500 Errors \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n error InvalidCallback();\\n error SlippageExceeded(uint256 required, uint256 maximum);\\n error InsufficientNativeValue();\\n error NativeRefundFailed();\\n error BzzTransferFailed();\\n error BzzApproveFailed();\\n error PoolNotFound();\\n error InvalidPath();\\n\\n // \\u2500\\u2500\\u2500 Structs \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n struct CreateBatchParams {\\n address owner;\\n address nodeAddress;\\n uint256 initialBalancePerChunk;\\n uint8 depth;\\n uint8 bucketDepth;\\n bytes32 nonce;\\n bool immutable_;\\n }\\n\\n /// @dev Packed into the `data` argument of pool.swap(); threaded through callback chains.\\n struct SwapCallbackData {\\n bytes path; // remaining path in exactOutput encoding (BZZ-first)\\n address payer; // who pays the input token (address(this) for native swaps)\\n uint256 maxAmountIn; // slippage ceiling for the final (tokenIn) leg\\n }\\n\\n // \\u2500\\u2500\\u2500 Constructor \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n constructor(address _stampsRegistry) {\\n stampsRegistry = IStampsRegistry(_stampsRegistry);\\n }\\n\\n receive() external payable {}\\n\\n // \\u2500\\u2500\\u2500 Quote Functions \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n // These modify state internally (Quoter simulates swaps) but are designed to\\n // be called via eth_call for free gas-less estimation.\\n\\n /**\\n * @notice Quote: how many `tokenIn` are needed to get exactly `bzzAmountOut` BZZ\\n * via a single-hop pool.\\n * @param tokenIn Input token (use WXDAI for native xDAI quotes)\\n * @param fee Pool fee tier (e.g. 500, 3000, 10000)\\n * @param bzzAmountOut Exact BZZ amount wanted\\n * @return amountIn Input tokens required (before slippage)\\n */\\n function quoteSingleHop(\\n address tokenIn,\\n uint24 fee,\\n uint256 bzzAmountOut\\n ) external returns (uint256 amountIn) {\\n (amountIn,,,) = IQuoterV2(SUSHI_QUOTER).quoteExactOutputSingle(\\n IQuoterV2.QuoteExactOutputSingleParams({\\n tokenIn: tokenIn,\\n tokenOut: BZZ,\\n amount: bzzAmountOut,\\n fee: fee,\\n sqrtPriceLimitX96: 0\\n })\\n );\\n }\\n\\n /**\\n * @notice Quote: how many input tokens are needed to get exactly `bzzAmountOut` BZZ\\n * via a multi-hop path.\\n * @param path Exact-output encoded path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\\n * @param bzzAmountOut Exact BZZ amount wanted\\n * @return amountIn Input tokens required (before slippage)\\n */\\n function quoteMultiHop(\\n bytes calldata path,\\n uint256 bzzAmountOut\\n ) external returns (uint256 amountIn) {\\n (amountIn,,,) = IQuoterV2(SUSHI_QUOTER).quoteExactOutput(path, bzzAmountOut);\\n }\\n\\n // \\u2500\\u2500\\u2500 Create Batch \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @notice Swap `tokenIn` \\u2192 BZZ via the given path and create a Swarm stamp batch.\\n * @dev `tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\\n * @param path Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\\n * @param maxAmountIn Maximum tokenIn to spend (slippage protection)\\n * @param bzzAmountOut Exact BZZ needed (= swarmBatchTotal = initialBalancePerChunk \\u00d7 2^depth)\\n * @param p Batch creation parameters\\n */\\n function createBatch(\\n bytes calldata path,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut,\\n CreateBatchParams calldata p\\n ) external {\\n _swapExactOutput(path, msg.sender, maxAmountIn, bzzAmountOut);\\n bytes32 batchId = _approveBzzAndCreate(p, bzzAmountOut);\\n address tokenIn = _lastToken(path);\\n emit BatchCreatedViaSwap(batchId, p.owner, tokenIn, maxAmountIn, bzzAmountOut);\\n }\\n\\n /**\\n * @notice Swap native xDAI \\u2192 BZZ and create a Swarm stamp batch.\\n * @dev Send msg.value \\u2265 maxAmountIn. Excess xDAI is refunded.\\n * @param path Exact-output path where the final token MUST be WXDAI:\\n * BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\\n * @param maxAmountIn Maximum xDAI to spend\\n * @param bzzAmountOut Exact BZZ needed\\n * @param p Batch creation parameters\\n */\\n function createBatchNative(\\n bytes calldata path,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut,\\n CreateBatchParams calldata p\\n ) external payable {\\n if (msg.value < maxAmountIn) revert InsufficientNativeValue();\\n IWXDAI(WXDAI).deposit{value: maxAmountIn}();\\n _swapExactOutput(path, address(this), maxAmountIn, bzzAmountOut);\\n bytes32 batchId = _approveBzzAndCreate(p, bzzAmountOut);\\n emit BatchCreatedViaSwap(batchId, p.owner, address(0), maxAmountIn, bzzAmountOut);\\n _refundNative();\\n }\\n\\n // \\u2500\\u2500\\u2500 Top Up Batch \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @notice Swap `tokenIn` \\u2192 BZZ and top up an existing Swarm stamp batch.\\n * @dev `tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\\n * @param path Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\\n * @param maxAmountIn Maximum tokenIn to spend\\n * @param bzzAmountOut Exact BZZ needed (= topupAmountPerChunk \\u00d7 2^depth)\\n * @param batchId Batch to top up\\n * @param topupAmountPerChunk Per-chunk top-up amount (matches registry call)\\n */\\n function topUp(\\n bytes calldata path,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut,\\n bytes32 batchId,\\n uint256 topupAmountPerChunk\\n ) external {\\n _swapExactOutput(path, msg.sender, maxAmountIn, bzzAmountOut);\\n _approveBzzAndTopUp(batchId, topupAmountPerChunk, bzzAmountOut);\\n address tokenIn = _lastToken(path);\\n emit BatchToppedUpViaSwap(batchId, tokenIn, maxAmountIn, bzzAmountOut);\\n }\\n\\n /**\\n * @notice Swap native xDAI \\u2192 BZZ and top up an existing Swarm stamp batch.\\n * @dev Send msg.value \\u2265 maxAmountIn. Excess xDAI is refunded.\\n * @param path BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\\n * @param maxAmountIn Maximum xDAI to spend\\n * @param bzzAmountOut Exact BZZ needed\\n * @param batchId Batch to top up\\n * @param topupAmountPerChunk Per-chunk top-up amount\\n */\\n function topUpNative(\\n bytes calldata path,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut,\\n bytes32 batchId,\\n uint256 topupAmountPerChunk\\n ) external payable {\\n if (msg.value < maxAmountIn) revert InsufficientNativeValue();\\n IWXDAI(WXDAI).deposit{value: maxAmountIn}();\\n _swapExactOutput(path, address(this), maxAmountIn, bzzAmountOut);\\n _approveBzzAndTopUp(batchId, topupAmountPerChunk, bzzAmountOut);\\n emit BatchToppedUpViaSwap(batchId, address(0), maxAmountIn, bzzAmountOut);\\n _refundNative();\\n }\\n\\n // \\u2500\\u2500\\u2500 Uniswap V3 / SushiSwap V3 Swap Callback \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @notice Called by a SushiSwap V3 pool during swap execution.\\n * @dev Implements the Uniswap V3 callback interface (SushiSwap V3 is compatible).\\n * For multi-hop swaps, this callback chains into the next pool swap before\\n * paying the current pool, routing tokens directly between pools.\\n */\\n function uniswapV3SwapCallback(\\n int256 amount0Delta,\\n int256 amount1Delta,\\n bytes calldata data\\n ) external {\\n require(amount0Delta > 0 || amount1Delta > 0, \\\"Zero deltas\\\");\\n\\n SwapCallbackData memory cb = abi.decode(data, (SwapCallbackData));\\n\\n // Decode the first pool in the path to verify the caller is legitimate.\\n (address tokenOut, uint24 fee, address tokenIn) = _decodeFirstPool(cb.path);\\n address expectedPool = ISushiV3Factory(SUSHI_FACTORY).getPool(tokenOut, tokenIn, fee);\\n if (msg.sender != expectedPool) revert InvalidCallback();\\n\\n // Determine which token we owe to the calling pool and how much.\\n // The positive delta is what the pool expects us to pay.\\n (address tokenOwed, uint256 amountOwed) = amount0Delta > 0\\n ? (ISushiV3Pool(msg.sender).token0(), uint256(amount0Delta))\\n : (ISushiV3Pool(msg.sender).token1(), uint256(amount1Delta));\\n\\n if (_hasMultiplePools(cb.path)) {\\n // Multi-hop: continue to next pool. Skip the first token from path to get\\n // the remaining sub-path: mid ++ fee ++ ... ++ tokenIn\\n bytes memory remainingPath = _skipToken(cb.path);\\n\\n // Decode the next pool info from remaining path.\\n (address nextTokenOut, uint24 nextFee, address nextTokenIn) = _decodeFirstPool(remainingPath);\\n address nextPool = ISushiV3Factory(SUSHI_FACTORY).getPool(nextTokenOut, nextTokenIn, nextFee);\\n if (nextPool == address(0)) revert PoolNotFound();\\n\\n // Swap in the next pool, sending output directly to msg.sender (current pool)\\n // so it receives the tokens it needs without going through this contract.\\n bool zeroForOne = nextTokenIn < nextTokenOut;\\n ISushiV3Pool(nextPool).swap(\\n msg.sender, // recipient = current pool (gets tokenOwed directly)\\n zeroForOne,\\n -int256(amountOwed), // exact output = amountOwed\\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\\n abi.encode(SwapCallbackData({\\n path: remainingPath,\\n payer: cb.payer,\\n maxAmountIn: cb.maxAmountIn\\n }))\\n );\\n } else {\\n // Final hop: pay tokenOwed from the original payer.\\n if (amountOwed > cb.maxAmountIn) {\\n revert SlippageExceeded(amountOwed, cb.maxAmountIn);\\n }\\n\\n if (cb.payer == address(this)) {\\n // Native xDAI flow: we already hold WXDAI from the deposit.\\n if (!IERC20(tokenOwed).transfer(msg.sender, amountOwed)) {\\n revert BzzTransferFailed();\\n }\\n } else {\\n // ERC20 flow: pull from user who pre-approved this contract.\\n if (!IERC20(tokenOwed).transferFrom(cb.payer, msg.sender, amountOwed)) {\\n revert BzzTransferFailed();\\n }\\n }\\n }\\n }\\n\\n // \\u2500\\u2500\\u2500 Internal Helpers \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @dev Execute an exact-output swap for `bzzAmountOut` BZZ using the given path.\\n * The path is in exactOutput encoding: BZZ ++ fee ++ [...] ++ tokenIn.\\n * BZZ lands in address(this) after the swap completes.\\n */\\n function _swapExactOutput(\\n bytes memory path,\\n address payer,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut\\n ) internal {\\n if (path.length < POP_OFFSET) revert InvalidPath();\\n\\n // Decode the first (and for single-hop, only) pool in the path.\\n (address tokenOut, uint24 fee, address tokenIn) = _decodeFirstPool(path);\\n if (tokenOut != BZZ) revert InvalidPath();\\n\\n address pool = ISushiV3Factory(SUSHI_FACTORY).getPool(tokenOut, tokenIn, fee);\\n if (pool == address(0)) revert PoolNotFound();\\n\\n // zeroForOne: true if tokenIn is token0 (address < BZZ)\\n bool zeroForOne = tokenIn < tokenOut;\\n\\n // amountSpecified < 0 \\u2192 exact output (we want exactly bzzAmountOut of BZZ)\\n ISushiV3Pool(pool).swap(\\n address(this), // receive BZZ here\\n zeroForOne,\\n -int256(bzzAmountOut),\\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\\n abi.encode(SwapCallbackData({\\n path: path,\\n payer: payer,\\n maxAmountIn: maxAmountIn\\n }))\\n );\\n }\\n\\n /**\\n * @dev Approve BZZ to the stamps registry and call createBatchRegistry.\\n * Returns the keccak256 batch ID consistent with the registry's derivation.\\n */\\n function _approveBzzAndCreate(\\n CreateBatchParams memory p,\\n uint256 bzzAmountOut\\n ) internal returns (bytes32 batchId) {\\n if (!IERC20(BZZ).approve(address(stampsRegistry), bzzAmountOut)) {\\n revert BzzApproveFailed();\\n }\\n\\n stampsRegistry.createBatchRegistry(\\n p.owner,\\n p.nodeAddress,\\n p.initialBalancePerChunk,\\n p.depth,\\n p.bucketDepth,\\n p.nonce,\\n p.immutable_\\n );\\n\\n // Registry derives batchId as keccak256(abi.encode(registry, nonce)).\\n batchId = keccak256(abi.encode(address(stampsRegistry), p.nonce));\\n }\\n\\n /**\\n * @dev Approve BZZ to the stamps registry and call topUpBatch.\\n */\\n function _approveBzzAndTopUp(\\n bytes32 batchId,\\n uint256 topupAmountPerChunk,\\n uint256 bzzAmountOut\\n ) internal {\\n if (!IERC20(BZZ).approve(address(stampsRegistry), bzzAmountOut)) {\\n revert BzzApproveFailed();\\n }\\n stampsRegistry.topUpBatch(batchId, topupAmountPerChunk);\\n }\\n\\n /**\\n * @dev Unwrap any remaining WXDAI and refund all native xDAI to msg.sender.\\n */\\n function _refundNative() internal {\\n uint256 wxdaiBalance = IERC20(WXDAI).balanceOf(address(this));\\n if (wxdaiBalance > 0) {\\n IWXDAI(WXDAI).withdraw(wxdaiBalance);\\n }\\n uint256 nativeBalance = address(this).balance;\\n if (nativeBalance > 0) {\\n (bool ok,) = msg.sender.call{value: nativeBalance}(\\\"\\\");\\n if (!ok) revert NativeRefundFailed();\\n }\\n }\\n\\n // \\u2500\\u2500\\u2500 Path Utilities \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @dev Returns true if the path encodes more than one pool (length > 43 bytes).\\n */\\n function _hasMultiplePools(bytes memory path) internal pure returns (bool) {\\n return path.length > POP_OFFSET;\\n }\\n\\n /**\\n * @dev Decodes the first pool segment from the path:\\n * tokenA (20 bytes) ++ fee (3 bytes) ++ tokenB (20 bytes)\\n */\\n function _decodeFirstPool(bytes memory path)\\n internal\\n pure\\n returns (address tokenA, uint24 fee, address tokenB)\\n {\\n tokenA = _toAddress(path, 0);\\n fee = _toUint24(path, ADDR_SIZE);\\n tokenB = _toAddress(path, NEXT_OFFSET);\\n }\\n\\n /**\\n * @dev Returns the path with the first token removed (skips ADDR_SIZE + FEE_SIZE bytes).\\n * Used to advance through multi-hop paths in the callback.\\n */\\n function _skipToken(bytes memory path) internal pure returns (bytes memory skipped) {\\n uint256 newLen = path.length - NEXT_OFFSET;\\n skipped = new bytes(newLen);\\n // Copy from offset NEXT_OFFSET onward\\n for (uint256 i = 0; i < newLen; i++) {\\n skipped[i] = path[i + NEXT_OFFSET];\\n }\\n }\\n\\n /**\\n * @dev Extracts the last 20-byte address from the path (the tokenIn address).\\n */\\n function _lastToken(bytes memory path) internal pure returns (address token) {\\n uint256 offset = path.length - ADDR_SIZE;\\n token = _toAddress(path, offset);\\n }\\n\\n /**\\n * @dev Reads a 20-byte address from `data` at `offset` using assembly.\\n * The address occupies bytes [offset, offset+20) and is right-aligned\\n * by shifting the 32-byte word 96 bits right.\\n */\\n function _toAddress(bytes memory data, uint256 offset) internal pure returns (address addr) {\\n assembly {\\n addr := shr(96, mload(add(add(data, 0x20), offset)))\\n }\\n }\\n\\n /**\\n * @dev Reads a 3-byte uint24 from `data` at `offset` using assembly.\\n * Shifts the 32-byte word 232 bits right to extract the top 3 bytes.\\n */\\n function _toUint24(bytes memory data, uint256 offset) internal pure returns (uint24 result) {\\n assembly {\\n result := shr(232, mload(add(add(data, 0x20), offset)))\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3ea468d65d7478e465dcbccb2363f6a16739508c6f2da61939432b8b5424b910\",\"license\":\"MIT\"}},\"version\":1}", - "bytecode": "0x60a03461007a57601f6118d738819003918201601f19168301916001600160401b0383118484101761007f5780849260209460405283398101031261007a57516001600160a01b0381169081900361007a57608052604051611841908161009682396080518181816105de0152818161137d01526115d00152f35b600080fd5b634e487b7160e01b600052604160045260246000fdfe6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c80630e70205d146100db5780630f43a7df146100d65780632a426602146100d15780632cb1dd93146100cc578063348fdcc4146100c7578063604a52fb146100c25780638acc27b9146100bd578063a7c086be146100b8578063b753bb47146100b3578063b972d70d146100ae578063eaa4f401146100a95763fa461e330361000e576107d6565b6107a7565b6106db565b6106ac565b61060d565b6105c8565b610599565b6104b1565b610390565b610237565b61018a565b6100f0565b60009103126100eb57565b600080fd5b346100eb5760003660031901126100eb57602060405173dbf3ea6f5bee45c02255b2c26a16f300502f68da8152f35b9181601f840112156100eb578235916001600160401b0383116100eb57602083818601950101116100eb57565b60a06003198201126100eb57600435906001600160401b0382116100eb576101769160040161011f565b909160243590604435906064359060843590565b346100eb576101ec7fc65f0fa5466fb164f60e47c03216e4cae4cf47cb57de7d15f9cbdd413028ace66101f36101f86102216101c53661014c565b91839894926101e682809a98969d949d336101e136898b610d4c565b61118c565b8b611366565b3691610d4c565b611466565b604080516001600160a01b03909216825260208201949094529283019190915281906060820190565b0390a2005b6001600160a01b038116036100eb57565b346100eb5760603660031901126100eb5760043561025481610226565b60243562ffffff811681036100eb57610317916102b5608092610287610278610d03565b6001600160a01b039094168452565b73dbf3ea6f5bee45c02255b2c26a16f300502f68da6020840152604435604084015262ffffff166060830152565b60008183015260408051635e90b82560e11b815282516001600160a01b039081166004830152602084015181166024830152918301516044820152606083015162ffffff166064820152608090920151166084820152918290819060a4820190565b0381600073b1e835dc2785b52265711e17fccb0fd018226a6e5af1801561038b5761035591600091610359575b506040519081529081906020820190565b0390f35b61037b915060803d608011610384575b6103738183610ce2565b810190610d94565b50505038610344565b503d610369565b610dc6565b6103993661014c565b90949383929334106104585773e91d153e0b41518a2ce8dd3d7944fa863463a97d91823b156100eb5760008493600460405180968193630d0e30db60e41b83525af1801561038b57858581946101e17fc65f0fa5466fb164f60e47c03216e4cae4cf47cb57de7d15f9cbdd413028ace69a6104279861042196610449575b5030923691610d4c565b86611366565b60408051600081526020810192909252810191909152606090a26100196114bd565b61045290610caf565b38610417565b604051631ac4c73760e11b8152600490fd5b6101406003198201126100eb576004356001600160401b0381116100eb57816104959160040161011f565b929092916024359160e06044359260631901126100eb57606490565b6104ba3661046a565b9291938434106104585773e91d153e0b41518a2ce8dd3d7944fa863463a97d90813b156100eb5760008692600460405180958193630d0e30db60e41b83525af191821561038b577f1ec8178b486abb6332d45b8822f76f29e264fc077dc6ae1d5db41f9a1f2be645946101e18593899361053c96610586575030923691610d4c565b61054f8161054a3686610df5565b6115b9565b926001600160a01b039061056290610e86565b604080516000815260208101979097528601929092521692606090a36100196114bd565b8061059361045292610caf565b806100e0565b346100eb5760003660031901126100eb57602060405173e91d153e0b41518a2ce8dd3d7944fa863463a97d8152f35b346100eb5760003660031901126100eb576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100eb576106a76106737f1ec8178b486abb6332d45b8822f76f29e264fc077dc6ae1d5db41f9a1f2be6456101f36106453661046a565b9792969194909361065d8587336101e1368d87610d4c565b61066b8561054a368c610df5565b973691610d4c565b95359161067f83610226565b604080516001600160a01b039889168152602081019590955284015294169381906060820190565b0390a3005b346100eb5760003660031901126100eb57602060405173f78031cbca409f2fb6876bdfdbc1b2df24cf9bef8152f35b346100eb5760403660031901126100eb576004356001600160401b0381116100eb57600061070f606492369060040161011f565b9283916040519485938492632f80bb1d60e01b84526040600485015281604485015284840137848382840101526024356024830152601f801991011681010301818373b1e835dc2785b52265711e17fccb0fd018226a6e5af1801561038b576103559160009161078a57506040519081529081906020820190565b61037b91503d806000833e61079f8183610ce2565b810190610f13565b346100eb5760003660031901126100eb57602060405173b1e835dc2785b52265711e17fccb0fd018226a6e8152f35b346100eb5760603660031901126100eb576004803590602435604435916001600160401b0383116100eb5761081161089f933690830161011f565b92909461083760009687831395868015610c90575b61082f90610fb9565b810190610ff3565b926108598451602081015160601c916037603483015160e81c92015160601c90565b909691946040978851998a91630b4c774160e11b9485845260209988850191939262ffffff90604092606085019660018060a01b03809216865216602085015216910152565b0392868a73f78031cbca409f2fb6876bdfdbc1b2df24cf9bef9581875afa998a1561038b578b9a610c71575b506001600160a01b03998a163303610c615715610c1a57508651630dfe168160e01b815285818581335afa90811561038b578a91610bfd575b505b865151602b1015610ace57508697869786610925610982989951611781565b9461094686602081015160601c916037603483015160e81c92015160601c90565b94519687526001600160a01b038083168a8901908152908616602082015262ffffff909116604082015290999395938492918391829160600190565b03915afa801561038b5782918c91610aa1575b5016958615610a915792610a1c6109bc8b999897969484610a38958f9816911610966110af565b978615610a76576401000276a49a5b81810151908b0151906109fb906001600160a01b03166109e9610d22565b9586526001600160a01b031683860152565b8a840152610a0e8a519384928301611105565b03601f198101835282610ce2565b8751630251596160e31b81529889978896879533908701611153565b03925af1801561038b57610a4b57505080f35b81610a6a92903d10610a6f575b610a628183610ce2565b81019061113d565b505080f35b503d610a58565b73fffd8963efd1fc6a506488495d951d5263988d259a6109cb565b89516301dbb3ff60e61b81528590fd5b610ac19150893d8b11610ac7575b610ab98183610ce2565b81019061106f565b38610995565b503d610aaf565b93929794958092508791500151808311610bd9575084015184929190879089906001600160a01b03168087163003610b815750875163a9059cbb60e01b8152338a820190815260208101949094529586949385935083906040010393165af191821561038b578592610b54575b505015610b4757505080f35b516304aa965560e41b8152fd5b610b739250803d10610b7a575b610b6b8183610ce2565b810190611084565b3880610b3b565b503d610b61565b88516323b872dd60e01b81526001600160a01b0390911692810192835233602084015260408301939093529194859392849290919083906060010393165af191821561038b578592610b5457505015610b4757505080f35b86516371c4efed60e01b815280890184815260208101929092529081906040010390fd5b610c149150863d8811610ac757610ab98183610ce2565b38610904565b875163d21220a760e01b815290945085818581335afa90811561038b578a91610c44575b50610906565b610c5b9150863d8811610ac757610ab98183610ce2565b38610c3e565b885163f7a632f560e01b81528590fd5b610c89919a50873d8911610ac757610ab98183610ce2565b98386108cb565b50888513610826565b634e487b7160e01b600052604160045260246000fd5b6001600160401b038111610cc257604052565b610c99565b606081019081106001600160401b03821117610cc257604052565b90601f801991011681019081106001600160401b03821117610cc257604052565b6040519060a082018281106001600160401b03821117610cc257604052565b60405190610d2f82610cc7565b565b6001600160401b038111610cc257601f01601f191660200190565b929192610d5882610d31565b91610d666040519384610ce2565b8294818452818301116100eb578281602093846000960137010152565b519063ffffffff821682036100eb57565b91908260809103126100eb578151916020810151610db181610226565b916060610dc060408401610d83565b92015190565b6040513d6000823e3d90fd5b359060ff821682036100eb57565b801515036100eb57565b3590610d2f82610de0565b91908260e09103126100eb5760405160e081018181106001600160401b03821117610cc25760405260c0610e818183958035610e3081610226565b85526020810135610e4081610226565b602086015260408101356040860152610e5b60608201610dd2565b6060860152610e6c60808201610dd2565b608086015260a081013560a086015201610dea565b910152565b35610e9081610226565b90565b6001600160401b038111610cc25760051b60200190565b9080601f830112156100eb57815190602091610ec581610e93565b93610ed36040519586610ce2565b81855260208086019260051b8201019283116100eb57602001905b828210610efc575050505090565b838091610f0884610d83565b815201910190610eee565b6080818303126100eb5780519260209283830151936001600160401b03948581116100eb5784019082601f830112156100eb57815191610f5283610e93565b92610f606040519485610ce2565b808452828085019160051b830101918583116100eb578301905b828210610fa057505050509360408401519081116100eb57606091610dc0918501610eaa565b8380918351610fae81610226565b815201910190610f7a565b15610fc057565b60405162461bcd60e51b815260206004820152600b60248201526a5a65726f2064656c74617360a81b6044820152606490fd5b906020828203126100eb5781356001600160401b03928382116100eb57016060818303126100eb576040519261102884610cc7565b81359081116100eb57810182601f820112156100eb5760409281602061105093359101610d4c565b8352602081013561106081610226565b60208401520135604082015290565b908160209103126100eb5751610e9081610226565b908160209103126100eb5751610e9081610de0565b634e487b7160e01b600052601160045260246000fd5b600160ff1b81146110c05760000390565b611099565b919082519283825260005b8481106110f1575050826000602080949584010152601f8019910116010190565b6020818301810151848301820152016110d0565b6020815260606040611122845183602086015260808501906110c5565b60208501516001600160a01b03168483015293015191015290565b91908260409103126100eb576020825192015190565b6001600160a01b039182168152911515602083015260408201929092529116606082015260a060808201819052610e90929101906110c5565b929091602b845110611354576111b884602081015160601c916037603483015160e81c92015160601c90565b9195919391906001600160a01b038088169173dbf3ea6f5bee45c02255b2c26a16f300502f68d91983016113545760408051630b4c774160e11b81526001600160a01b039a8b16600482015299881660248b015262ffffff9190911660448a01529760208160648173f78031cbca409f2fb6876bdfdbc1b2df24cf9bef5afa801561038b578291600091611335575b5016958615611324576112ac97959361126c6000948b9997946112ba941610936110af565b948385146113065761129a6401000276a4985b611287610d22565b9384526001600160a01b03166020840152565b88820152875198899160208301611105565b03601f198101895288610ce2565b6112da865197889687958694630251596160e31b86523060048701611153565b03925af1801561038b576112ec575050565b8161130292903d10610a6f57610a628183610ce2565b5050565b61129a73fffd8963efd1fc6a506488495d951d5263988d259861127f565b88516301dbb3ff60e61b8152600490fd5b61134e915060203d602011610ac757610ab98183610ce2565b38611247565b6040516320db826760e01b8152600490fd5b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482018190526024820194909452909392600092916020816044818773dbf3ea6f5bee45c02255b2c26a16f300502f68da5af190811561038b578491611447575b501561143557803b1561143157604051631549361960e01b8152600481019590955260248501919091529192918290604490829084905af1801561038b576114245750565b80610593610d2f92610caf565b8280fd5b604051632546bed360e01b8152600490fd5b611460915060203d602011610b7a57610b6b8183610ce2565b386113df565b8051806013198101116110c05701600c015160601c90565b908160209103126100eb575190565b3d156114b8573d9061149e82610d31565b916114ac6040519384610ce2565b82523d6000602084013e565b606090565b6040516370a0823160e01b815230600482015273e91d153e0b41518a2ce8dd3d7944fa863463a97d602082602481845afa91821561038b57600092611588575b508161153a575b5050478061150f5750565b600080808093335af161152061148d565b501561152857565b6040516308520d7160e41b8152600490fd5b803b156100eb57604051632e1a7d4d60e01b815260048101929092526000908290602490829084905af1801561038b57611575575b80611504565b8061059361158292610caf565b3861156f565b6115ab91925060203d6020116115b2575b6115a38183610ce2565b81019061147e565b90386114fd565b503d611599565b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482018190526024820193909352600091906020816044818673dbf3ea6f5bee45c02255b2c26a16f300502f68da5af190811561038b57839161173b575b50156114355780516001600160a01b031660208201519092906001600160a01b0316604083015193611665606085015160ff1690565b608085015160ff169460a081019561168260c08851930151151590565b92893b15611737576040516317f8c86b60e11b81526001600160a01b039586166004820152959094166024860152604485019790975260ff928316606485015291909516608483015260a482015292151560c48401528260e48183875af190811561038b57610a0e9261171e92611724575b5051604080516001600160a01b039095166020860190815290850191909152929182906060820190565b51902090565b8061059361173192610caf565b386116f4565b8680fd5b611754915060203d602011610b7a57610b6b8183610ce2565b3861162f565b90815181101561176b570160200190565b634e487b7160e01b600052603260045260246000fd5b8051601619810192919083116110c05761179a83610d31565b926117a86040519485610ce2565b808452601f196117b782610d31565b013660208601378360009260005b8381106117d3575050505050565b60178101908181116110c0576001916001600160f81b0319906117f6908561175a565b5116861a611804828661175a565b53016117c556fea2646970667358221220c1bcb94f145b5031b5241b079ab267fc10582dc6d84e331e4ed3116a4e43b44b64736f6c63430008170033", - "deployedBytecode": "0x6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c80630e70205d146100db5780630f43a7df146100d65780632a426602146100d15780632cb1dd93146100cc578063348fdcc4146100c7578063604a52fb146100c25780638acc27b9146100bd578063a7c086be146100b8578063b753bb47146100b3578063b972d70d146100ae578063eaa4f401146100a95763fa461e330361000e576107d6565b6107a7565b6106db565b6106ac565b61060d565b6105c8565b610599565b6104b1565b610390565b610237565b61018a565b6100f0565b60009103126100eb57565b600080fd5b346100eb5760003660031901126100eb57602060405173dbf3ea6f5bee45c02255b2c26a16f300502f68da8152f35b9181601f840112156100eb578235916001600160401b0383116100eb57602083818601950101116100eb57565b60a06003198201126100eb57600435906001600160401b0382116100eb576101769160040161011f565b909160243590604435906064359060843590565b346100eb576101ec7fc65f0fa5466fb164f60e47c03216e4cae4cf47cb57de7d15f9cbdd413028ace66101f36101f86102216101c53661014c565b91839894926101e682809a98969d949d336101e136898b610d4c565b61118c565b8b611366565b3691610d4c565b611466565b604080516001600160a01b03909216825260208201949094529283019190915281906060820190565b0390a2005b6001600160a01b038116036100eb57565b346100eb5760603660031901126100eb5760043561025481610226565b60243562ffffff811681036100eb57610317916102b5608092610287610278610d03565b6001600160a01b039094168452565b73dbf3ea6f5bee45c02255b2c26a16f300502f68da6020840152604435604084015262ffffff166060830152565b60008183015260408051635e90b82560e11b815282516001600160a01b039081166004830152602084015181166024830152918301516044820152606083015162ffffff166064820152608090920151166084820152918290819060a4820190565b0381600073b1e835dc2785b52265711e17fccb0fd018226a6e5af1801561038b5761035591600091610359575b506040519081529081906020820190565b0390f35b61037b915060803d608011610384575b6103738183610ce2565b810190610d94565b50505038610344565b503d610369565b610dc6565b6103993661014c565b90949383929334106104585773e91d153e0b41518a2ce8dd3d7944fa863463a97d91823b156100eb5760008493600460405180968193630d0e30db60e41b83525af1801561038b57858581946101e17fc65f0fa5466fb164f60e47c03216e4cae4cf47cb57de7d15f9cbdd413028ace69a6104279861042196610449575b5030923691610d4c565b86611366565b60408051600081526020810192909252810191909152606090a26100196114bd565b61045290610caf565b38610417565b604051631ac4c73760e11b8152600490fd5b6101406003198201126100eb576004356001600160401b0381116100eb57816104959160040161011f565b929092916024359160e06044359260631901126100eb57606490565b6104ba3661046a565b9291938434106104585773e91d153e0b41518a2ce8dd3d7944fa863463a97d90813b156100eb5760008692600460405180958193630d0e30db60e41b83525af191821561038b577f1ec8178b486abb6332d45b8822f76f29e264fc077dc6ae1d5db41f9a1f2be645946101e18593899361053c96610586575030923691610d4c565b61054f8161054a3686610df5565b6115b9565b926001600160a01b039061056290610e86565b604080516000815260208101979097528601929092521692606090a36100196114bd565b8061059361045292610caf565b806100e0565b346100eb5760003660031901126100eb57602060405173e91d153e0b41518a2ce8dd3d7944fa863463a97d8152f35b346100eb5760003660031901126100eb576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100eb576106a76106737f1ec8178b486abb6332d45b8822f76f29e264fc077dc6ae1d5db41f9a1f2be6456101f36106453661046a565b9792969194909361065d8587336101e1368d87610d4c565b61066b8561054a368c610df5565b973691610d4c565b95359161067f83610226565b604080516001600160a01b039889168152602081019590955284015294169381906060820190565b0390a3005b346100eb5760003660031901126100eb57602060405173f78031cbca409f2fb6876bdfdbc1b2df24cf9bef8152f35b346100eb5760403660031901126100eb576004356001600160401b0381116100eb57600061070f606492369060040161011f565b9283916040519485938492632f80bb1d60e01b84526040600485015281604485015284840137848382840101526024356024830152601f801991011681010301818373b1e835dc2785b52265711e17fccb0fd018226a6e5af1801561038b576103559160009161078a57506040519081529081906020820190565b61037b91503d806000833e61079f8183610ce2565b810190610f13565b346100eb5760003660031901126100eb57602060405173b1e835dc2785b52265711e17fccb0fd018226a6e8152f35b346100eb5760603660031901126100eb576004803590602435604435916001600160401b0383116100eb5761081161089f933690830161011f565b92909461083760009687831395868015610c90575b61082f90610fb9565b810190610ff3565b926108598451602081015160601c916037603483015160e81c92015160601c90565b909691946040978851998a91630b4c774160e11b9485845260209988850191939262ffffff90604092606085019660018060a01b03809216865216602085015216910152565b0392868a73f78031cbca409f2fb6876bdfdbc1b2df24cf9bef9581875afa998a1561038b578b9a610c71575b506001600160a01b03998a163303610c615715610c1a57508651630dfe168160e01b815285818581335afa90811561038b578a91610bfd575b505b865151602b1015610ace57508697869786610925610982989951611781565b9461094686602081015160601c916037603483015160e81c92015160601c90565b94519687526001600160a01b038083168a8901908152908616602082015262ffffff909116604082015290999395938492918391829160600190565b03915afa801561038b5782918c91610aa1575b5016958615610a915792610a1c6109bc8b999897969484610a38958f9816911610966110af565b978615610a76576401000276a49a5b81810151908b0151906109fb906001600160a01b03166109e9610d22565b9586526001600160a01b031683860152565b8a840152610a0e8a519384928301611105565b03601f198101835282610ce2565b8751630251596160e31b81529889978896879533908701611153565b03925af1801561038b57610a4b57505080f35b81610a6a92903d10610a6f575b610a628183610ce2565b81019061113d565b505080f35b503d610a58565b73fffd8963efd1fc6a506488495d951d5263988d259a6109cb565b89516301dbb3ff60e61b81528590fd5b610ac19150893d8b11610ac7575b610ab98183610ce2565b81019061106f565b38610995565b503d610aaf565b93929794958092508791500151808311610bd9575084015184929190879089906001600160a01b03168087163003610b815750875163a9059cbb60e01b8152338a820190815260208101949094529586949385935083906040010393165af191821561038b578592610b54575b505015610b4757505080f35b516304aa965560e41b8152fd5b610b739250803d10610b7a575b610b6b8183610ce2565b810190611084565b3880610b3b565b503d610b61565b88516323b872dd60e01b81526001600160a01b0390911692810192835233602084015260408301939093529194859392849290919083906060010393165af191821561038b578592610b5457505015610b4757505080f35b86516371c4efed60e01b815280890184815260208101929092529081906040010390fd5b610c149150863d8811610ac757610ab98183610ce2565b38610904565b875163d21220a760e01b815290945085818581335afa90811561038b578a91610c44575b50610906565b610c5b9150863d8811610ac757610ab98183610ce2565b38610c3e565b885163f7a632f560e01b81528590fd5b610c89919a50873d8911610ac757610ab98183610ce2565b98386108cb565b50888513610826565b634e487b7160e01b600052604160045260246000fd5b6001600160401b038111610cc257604052565b610c99565b606081019081106001600160401b03821117610cc257604052565b90601f801991011681019081106001600160401b03821117610cc257604052565b6040519060a082018281106001600160401b03821117610cc257604052565b60405190610d2f82610cc7565b565b6001600160401b038111610cc257601f01601f191660200190565b929192610d5882610d31565b91610d666040519384610ce2565b8294818452818301116100eb578281602093846000960137010152565b519063ffffffff821682036100eb57565b91908260809103126100eb578151916020810151610db181610226565b916060610dc060408401610d83565b92015190565b6040513d6000823e3d90fd5b359060ff821682036100eb57565b801515036100eb57565b3590610d2f82610de0565b91908260e09103126100eb5760405160e081018181106001600160401b03821117610cc25760405260c0610e818183958035610e3081610226565b85526020810135610e4081610226565b602086015260408101356040860152610e5b60608201610dd2565b6060860152610e6c60808201610dd2565b608086015260a081013560a086015201610dea565b910152565b35610e9081610226565b90565b6001600160401b038111610cc25760051b60200190565b9080601f830112156100eb57815190602091610ec581610e93565b93610ed36040519586610ce2565b81855260208086019260051b8201019283116100eb57602001905b828210610efc575050505090565b838091610f0884610d83565b815201910190610eee565b6080818303126100eb5780519260209283830151936001600160401b03948581116100eb5784019082601f830112156100eb57815191610f5283610e93565b92610f606040519485610ce2565b808452828085019160051b830101918583116100eb578301905b828210610fa057505050509360408401519081116100eb57606091610dc0918501610eaa565b8380918351610fae81610226565b815201910190610f7a565b15610fc057565b60405162461bcd60e51b815260206004820152600b60248201526a5a65726f2064656c74617360a81b6044820152606490fd5b906020828203126100eb5781356001600160401b03928382116100eb57016060818303126100eb576040519261102884610cc7565b81359081116100eb57810182601f820112156100eb5760409281602061105093359101610d4c565b8352602081013561106081610226565b60208401520135604082015290565b908160209103126100eb5751610e9081610226565b908160209103126100eb5751610e9081610de0565b634e487b7160e01b600052601160045260246000fd5b600160ff1b81146110c05760000390565b611099565b919082519283825260005b8481106110f1575050826000602080949584010152601f8019910116010190565b6020818301810151848301820152016110d0565b6020815260606040611122845183602086015260808501906110c5565b60208501516001600160a01b03168483015293015191015290565b91908260409103126100eb576020825192015190565b6001600160a01b039182168152911515602083015260408201929092529116606082015260a060808201819052610e90929101906110c5565b929091602b845110611354576111b884602081015160601c916037603483015160e81c92015160601c90565b9195919391906001600160a01b038088169173dbf3ea6f5bee45c02255b2c26a16f300502f68d91983016113545760408051630b4c774160e11b81526001600160a01b039a8b16600482015299881660248b015262ffffff9190911660448a01529760208160648173f78031cbca409f2fb6876bdfdbc1b2df24cf9bef5afa801561038b578291600091611335575b5016958615611324576112ac97959361126c6000948b9997946112ba941610936110af565b948385146113065761129a6401000276a4985b611287610d22565b9384526001600160a01b03166020840152565b88820152875198899160208301611105565b03601f198101895288610ce2565b6112da865197889687958694630251596160e31b86523060048701611153565b03925af1801561038b576112ec575050565b8161130292903d10610a6f57610a628183610ce2565b5050565b61129a73fffd8963efd1fc6a506488495d951d5263988d259861127f565b88516301dbb3ff60e61b8152600490fd5b61134e915060203d602011610ac757610ab98183610ce2565b38611247565b6040516320db826760e01b8152600490fd5b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482018190526024820194909452909392600092916020816044818773dbf3ea6f5bee45c02255b2c26a16f300502f68da5af190811561038b578491611447575b501561143557803b1561143157604051631549361960e01b8152600481019590955260248501919091529192918290604490829084905af1801561038b576114245750565b80610593610d2f92610caf565b8280fd5b604051632546bed360e01b8152600490fd5b611460915060203d602011610b7a57610b6b8183610ce2565b386113df565b8051806013198101116110c05701600c015160601c90565b908160209103126100eb575190565b3d156114b8573d9061149e82610d31565b916114ac6040519384610ce2565b82523d6000602084013e565b606090565b6040516370a0823160e01b815230600482015273e91d153e0b41518a2ce8dd3d7944fa863463a97d602082602481845afa91821561038b57600092611588575b508161153a575b5050478061150f5750565b600080808093335af161152061148d565b501561152857565b6040516308520d7160e41b8152600490fd5b803b156100eb57604051632e1a7d4d60e01b815260048101929092526000908290602490829084905af1801561038b57611575575b80611504565b8061059361158292610caf565b3861156f565b6115ab91925060203d6020116115b2575b6115a38183610ce2565b81019061147e565b90386114fd565b503d611599565b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482018190526024820193909352600091906020816044818673dbf3ea6f5bee45c02255b2c26a16f300502f68da5af190811561038b57839161173b575b50156114355780516001600160a01b031660208201519092906001600160a01b0316604083015193611665606085015160ff1690565b608085015160ff169460a081019561168260c08851930151151590565b92893b15611737576040516317f8c86b60e11b81526001600160a01b039586166004820152959094166024860152604485019790975260ff928316606485015291909516608483015260a482015292151560c48401528260e48183875af190811561038b57610a0e9261171e92611724575b5051604080516001600160a01b039095166020860190815290850191909152929182906060820190565b51902090565b8061059361173192610caf565b386116f4565b8680fd5b611754915060203d602011610b7a57610b6b8183610ce2565b3861162f565b90815181101561176b570160200190565b634e487b7160e01b600052603260045260246000fd5b8051601619810192919083116110c05761179a83610d31565b926117a86040519485610ce2565b808452601f196117b782610d31565b013660208601378360009260005b8381106117d3575050505050565b60178101908181116110c0576001916001600160f81b0319906117f6908561175a565b5116861a611804828661175a565b53016117c556fea2646970667358221220c1bcb94f145b5031b5241b079ab267fc10582dc6d84e331e4ed3116a4e43b44b64736f6c63430008170033", + "solcInputHash": "255f96c59429e1e56480af0cdaa74e3a", + "metadata": "{\"compiler\":{\"version\":\"0.8.23+commit.f704f362\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_stampsRegistry\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"BzzApproveFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BzzTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientNativeValue\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCallback\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPath\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NativeRefundFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PoolNotFound\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"required\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maximum\",\"type\":\"uint256\"}],\"name\":\"SlippageExceeded\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"bzzAmount\",\"type\":\"uint256\"}],\"name\":\"BatchCreatedViaSwap\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"bzzAmount\",\"type\":\"uint256\"}],\"name\":\"BatchToppedUpViaSwap\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BZZ\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SUSHI_FACTORY\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SUSHI_QUOTER\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"WXDAI\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nodeAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"initialBalancePerChunk\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"depth\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"bucketDepth\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"immutable_\",\"type\":\"bool\"}],\"internalType\":\"struct SushiSwapStampsRouter.CreateBatchParams\",\"name\":\"p\",\"type\":\"tuple\"}],\"name\":\"createBatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"nodeAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"initialBalancePerChunk\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"depth\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"bucketDepth\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"immutable_\",\"type\":\"bool\"}],\"internalType\":\"struct SushiSwapStampsRouter.CreateBatchParams\",\"name\":\"p\",\"type\":\"tuple\"}],\"name\":\"createBatchNative\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"}],\"name\":\"quoteMultiHop\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"}],\"name\":\"quoteSingleHop\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stampsRegistry\",\"outputs\":[{\"internalType\":\"contract IStampsRegistry\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"topupAmountPerChunk\",\"type\":\"uint256\"}],\"name\":\"topUp\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"path\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"maxAmountIn\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bzzAmountOut\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"batchId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"topupAmountPerChunk\",\"type\":\"uint256\"}],\"name\":\"topUpNative\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"amount0Delta\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1Delta\",\"type\":\"int256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"uniswapV3SwapCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"createBatch(bytes,uint256,uint256,(address,address,uint256,uint8,uint8,bytes32,bool))\":{\"details\":\"`tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\",\"params\":{\"bzzAmountOut\":\"Exact BZZ needed (= swarmBatchTotal = initialBalancePerChunk \\u00d7 2^depth)\",\"maxAmountIn\":\"Maximum tokenIn to spend (slippage protection)\",\"p\":\"Batch creation parameters\",\"path\":\"Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\"}},\"createBatchNative(bytes,uint256,uint256,(address,address,uint256,uint8,uint8,bytes32,bool))\":{\"details\":\"Send msg.value \\u2265 maxAmountIn. Excess xDAI is refunded.\",\"params\":{\"bzzAmountOut\":\"Exact BZZ needed\",\"maxAmountIn\":\"Maximum xDAI to spend\",\"p\":\"Batch creation parameters\",\"path\":\"Exact-output path where the final token MUST be WXDAI: BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\"}},\"quoteMultiHop(bytes,uint256)\":{\"params\":{\"bzzAmountOut\":\"Exact BZZ amount wanted\",\"path\":\"Exact-output encoded path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\"},\"returns\":{\"amountIn\":\" Input tokens required (before slippage)\"}},\"quoteSingleHop(address,uint24,uint256)\":{\"params\":{\"bzzAmountOut\":\"Exact BZZ amount wanted\",\"fee\":\"Pool fee tier (e.g. 500, 3000, 10000)\",\"tokenIn\":\"Input token (use WXDAI for native xDAI quotes)\"},\"returns\":{\"amountIn\":\" Input tokens required (before slippage)\"}},\"topUp(bytes,uint256,uint256,bytes32,uint256)\":{\"details\":\"`tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\",\"params\":{\"batchId\":\"Batch to top up\",\"bzzAmountOut\":\"Exact BZZ needed (= topupAmountPerChunk \\u00d7 2^depth)\",\"maxAmountIn\":\"Maximum tokenIn to spend\",\"path\":\"Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\",\"topupAmountPerChunk\":\"Per-chunk top-up amount (matches registry call)\"}},\"topUpNative(bytes,uint256,uint256,bytes32,uint256)\":{\"details\":\"Send msg.value \\u2265 maxAmountIn. Excess xDAI is refunded.\",\"params\":{\"batchId\":\"Batch to top up\",\"bzzAmountOut\":\"Exact BZZ needed\",\"maxAmountIn\":\"Maximum xDAI to spend\",\"path\":\"BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\",\"topupAmountPerChunk\":\"Per-chunk top-up amount\"}},\"uniswapV3SwapCallback(int256,int256,bytes)\":{\"details\":\"Implements the Uniswap V3 callback interface (SushiSwap V3 is compatible). For multi-hop swaps, this callback chains into the next pool swap before paying the current pool, routing tokens directly between pools.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"BZZ()\":{\"notice\":\"BZZ token on Gnosis\"},\"SUSHI_FACTORY()\":{\"notice\":\"SushiSwap V3 Factory on Gnosis\"},\"SUSHI_QUOTER()\":{\"notice\":\"SushiSwap V3 QuoterV2 on Gnosis\"},\"WXDAI()\":{\"notice\":\"Wrapped xDAI on Gnosis\"},\"createBatch(bytes,uint256,uint256,(address,address,uint256,uint8,uint8,bytes32,bool))\":{\"notice\":\"Swap `tokenIn` \\u2192 BZZ via the given path and create a Swarm stamp batch.\"},\"createBatchNative(bytes,uint256,uint256,(address,address,uint256,uint8,uint8,bytes32,bool))\":{\"notice\":\"Swap native xDAI \\u2192 BZZ and create a Swarm stamp batch.\"},\"quoteMultiHop(bytes,uint256)\":{\"notice\":\"Quote: how many input tokens are needed to get exactly `bzzAmountOut` BZZ via a multi-hop path.\"},\"quoteSingleHop(address,uint24,uint256)\":{\"notice\":\"Quote: how many `tokenIn` are needed to get exactly `bzzAmountOut` BZZ via a single-hop pool.\"},\"topUp(bytes,uint256,uint256,bytes32,uint256)\":{\"notice\":\"Swap `tokenIn` \\u2192 BZZ and top up an existing Swarm stamp batch.\"},\"topUpNative(bytes,uint256,uint256,bytes32,uint256)\":{\"notice\":\"Swap native xDAI \\u2192 BZZ and top up an existing Swarm stamp batch.\"},\"uniswapV3SwapCallback(int256,int256,bytes)\":{\"notice\":\"Called by a SushiSwap V3 pool during swap execution.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/SushiSwapStampsRouter.sol\":\"SushiSwapStampsRouter\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[],\"viaIR\":true},\"sources\":{\"contracts/SushiSwapStampsRouter.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.23;\\n\\n/*\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2557 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\n \\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u255d\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u255a\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u255a\\u2588\\u2588\\u2588\\u2554\\u2588\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\n \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d\\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\u255a\\u2550\\u255d\\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u255d\\u255a\\u2550\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\u255a\\u2550\\u255d\\n\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u255a\\u2550\\u2550\\u2588\\u2588\\u2554\\u2550\\u2550\\u255d\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2588\\u2588\\u2588\\u2588\\u2554\\u2588\\u2588\\u2551\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\n \\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2551\\u2588\\u2588\\u2551\\u255a\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2551\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2588\\u2588\\u2551\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u255a\\u2550\\u255d \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2551\\n \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\u255a\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d\\n\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557 \\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u255a\\u2550\\u2550\\u2588\\u2588\\u2554\\u2550\\u2550\\u255d\\u2588\\u2588\\u2554\\u2550\\u2550\\u2550\\u2550\\u255d\\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2557 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\n \\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2554\\u2550\\u2550\\u255d \\u2588\\u2588\\u2554\\u2550\\u2550\\u2588\\u2588\\u2557\\n \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\u255a\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d\\u255a\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2554\\u255d \\u2588\\u2588\\u2551 \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2557\\u2588\\u2588\\u2551 \\u2588\\u2588\\u2551\\n \\u255a\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d \\u255a\\u2550\\u255d \\u255a\\u2550\\u2550\\u2550\\u2550\\u2550\\u2550\\u255d\\u255a\\u2550\\u255d \\u255a\\u2550\\u255d\\n*/\\n\\n/**\\n * @title SushiSwapStampsRouter\\n * @notice Swaps any Gnosis-chain token to BZZ via SushiSwap V3 and atomically\\n * creates or tops up a Swarm postage-stamp batch in a single transaction.\\n *\\n * @dev Implements the Uniswap V3 callback interface (SushiSwap V3 is fully compatible).\\n * Supports both single-hop and multi-hop exact-output swaps via path encoding.\\n *\\n * Path encoding for exactOutput swaps (reversed token order):\\n * single-hop: BZZ ++ uint24(fee) ++ tokenIn (43 bytes)\\n * two-hop: BZZ ++ uint24(fee2) ++ mid ++ uint24(fee1) ++ tokenIn (66 bytes)\\n *\\n * Quote functions are non-view (Quoter simulates swaps internally) but are\\n * designed to be called via eth_call for gas-free estimation.\\n *\\n * Gnosis-chain addresses (hardcoded):\\n * BZZ = 0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da\\n * WXDAI = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d\\n * Quoter = 0xb1e835dc2785b52265711e17fccb0fd018226a6e (SushiSwap V3 QuoterV2)\\n * Factory= 0xf78031cbca409f2fb6876bdfdbc1b2df24cf9bef (SushiSwap V3 Factory)\\n */\\n\\n// \\u2500\\u2500\\u2500 Interfaces \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\ninterface IERC20 {\\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n function approve(address spender, uint256 amount) external returns (bool);\\n function balanceOf(address account) external view returns (uint256);\\n}\\n\\ninterface IWXDAI {\\n function deposit() external payable;\\n function withdraw(uint256 amount) external;\\n}\\n\\ninterface ISushiV3Pool {\\n function swap(\\n address recipient,\\n bool zeroForOne,\\n int256 amountSpecified,\\n uint160 sqrtPriceLimitX96,\\n bytes calldata data\\n ) external returns (int256 amount0, int256 amount1);\\n\\n function token0() external view returns (address);\\n function token1() external view returns (address);\\n}\\n\\ninterface ISushiV3Factory {\\n function getPool(\\n address tokenA,\\n address tokenB,\\n uint24 fee\\n ) external view returns (address pool);\\n}\\n\\ninterface IQuoterV2 {\\n struct QuoteExactOutputSingleParams {\\n address tokenIn;\\n address tokenOut;\\n uint256 amount;\\n uint24 fee;\\n uint160 sqrtPriceLimitX96;\\n }\\n\\n function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params)\\n external\\n returns (\\n uint256 amountIn,\\n uint160 sqrtPriceX96After,\\n uint32 initializedTicksCrossed,\\n uint256 gasEstimate\\n );\\n\\n function quoteExactOutput(bytes memory path, uint256 amountOut)\\n external\\n returns (\\n uint256 amountIn,\\n uint160[] memory sqrtPriceX96AfterList,\\n uint32[] memory initializedTicksCrossedList,\\n uint256 gasEstimate\\n );\\n}\\n\\ninterface IStampsRegistry {\\n function createBatchRegistry(\\n address _owner,\\n address _nodeAddress,\\n uint256 _initialBalancePerChunk,\\n uint8 _depth,\\n uint8 _bucketDepth,\\n bytes32 _nonce,\\n bool _immutable\\n ) external;\\n\\n function topUpBatch(bytes32 _batchId, uint256 _topupAmountPerChunk) external;\\n}\\n\\n// \\u2500\\u2500\\u2500 Router Contract \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\ncontract SushiSwapStampsRouter {\\n\\n // \\u2500\\u2500\\u2500 Constants \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /// @notice BZZ token on Gnosis\\n address public constant BZZ = 0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da;\\n\\n /// @notice Wrapped xDAI on Gnosis\\n address public constant WXDAI = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d;\\n\\n /// @notice SushiSwap V3 QuoterV2 on Gnosis\\n address public constant SUSHI_QUOTER = 0xb1E835Dc2785b52265711e17fCCb0fd018226a6e;\\n\\n /// @notice SushiSwap V3 Factory on Gnosis\\n address public constant SUSHI_FACTORY = 0xf78031CBCA409F2FB6876BDFDBc1b2df24cF9bEf;\\n\\n /// @notice Minimum sqrt price limit (used when selling token0 \\u2192 token1, zeroForOne=true)\\n uint160 internal constant MIN_SQRT_RATIO = 4295128739;\\n\\n /// @notice Maximum sqrt price limit (used when selling token1 \\u2192 token0, zeroForOne=false)\\n uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;\\n\\n // Path encoding offsets (bytes): address=20, fee=3, nextOffset=23, popOffset=43\\n uint256 private constant ADDR_SIZE = 20;\\n uint256 private constant FEE_SIZE = 3;\\n uint256 private constant NEXT_OFFSET = 23; // ADDR_SIZE + FEE_SIZE\\n uint256 private constant POP_OFFSET = 43; // NEXT_OFFSET + ADDR_SIZE\\n\\n // \\u2500\\u2500\\u2500 Immutables \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n IStampsRegistry public immutable stampsRegistry;\\n\\n // \\u2500\\u2500\\u2500 Events \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n event BatchCreatedViaSwap(\\n bytes32 indexed batchId,\\n address indexed owner,\\n address tokenIn,\\n uint256 amountIn,\\n uint256 bzzAmount\\n );\\n\\n event BatchToppedUpViaSwap(\\n bytes32 indexed batchId,\\n address tokenIn,\\n uint256 amountIn,\\n uint256 bzzAmount\\n );\\n\\n // \\u2500\\u2500\\u2500 Errors \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n error InvalidCallback();\\n error SlippageExceeded(uint256 required, uint256 maximum);\\n error InsufficientNativeValue();\\n error NativeRefundFailed();\\n error BzzTransferFailed();\\n error BzzApproveFailed();\\n error PoolNotFound();\\n error InvalidPath();\\n\\n // \\u2500\\u2500\\u2500 Structs \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n struct CreateBatchParams {\\n address owner;\\n address nodeAddress;\\n uint256 initialBalancePerChunk;\\n uint8 depth;\\n uint8 bucketDepth;\\n bytes32 nonce;\\n bool immutable_;\\n }\\n\\n /// @dev Packed into the `data` argument of pool.swap(); threaded through callback chains.\\n struct SwapCallbackData {\\n bytes path; // remaining path in exactOutput encoding (BZZ-first)\\n address payer; // who pays the input token (address(this) for native swaps)\\n uint256 maxAmountIn; // slippage ceiling for the final (tokenIn) leg\\n }\\n\\n // \\u2500\\u2500\\u2500 Constructor \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n constructor(address _stampsRegistry) {\\n require(_stampsRegistry != address(0), \\\"zero registry\\\");\\n stampsRegistry = IStampsRegistry(_stampsRegistry);\\n }\\n\\n receive() external payable {}\\n\\n // \\u2500\\u2500\\u2500 Quote Functions \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n // These modify state internally (Quoter simulates swaps) but are designed to\\n // be called via eth_call for free gas-less estimation.\\n\\n /**\\n * @notice Quote: how many `tokenIn` are needed to get exactly `bzzAmountOut` BZZ\\n * via a single-hop pool.\\n * @param tokenIn Input token (use WXDAI for native xDAI quotes)\\n * @param fee Pool fee tier (e.g. 500, 3000, 10000)\\n * @param bzzAmountOut Exact BZZ amount wanted\\n * @return amountIn Input tokens required (before slippage)\\n */\\n function quoteSingleHop(\\n address tokenIn,\\n uint24 fee,\\n uint256 bzzAmountOut\\n ) external returns (uint256 amountIn) {\\n (amountIn,,,) = IQuoterV2(SUSHI_QUOTER).quoteExactOutputSingle(\\n IQuoterV2.QuoteExactOutputSingleParams({\\n tokenIn: tokenIn,\\n tokenOut: BZZ,\\n amount: bzzAmountOut,\\n fee: fee,\\n sqrtPriceLimitX96: 0\\n })\\n );\\n }\\n\\n /**\\n * @notice Quote: how many input tokens are needed to get exactly `bzzAmountOut` BZZ\\n * via a multi-hop path.\\n * @param path Exact-output encoded path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\\n * @param bzzAmountOut Exact BZZ amount wanted\\n * @return amountIn Input tokens required (before slippage)\\n */\\n function quoteMultiHop(\\n bytes calldata path,\\n uint256 bzzAmountOut\\n ) external returns (uint256 amountIn) {\\n (amountIn,,,) = IQuoterV2(SUSHI_QUOTER).quoteExactOutput(path, bzzAmountOut);\\n }\\n\\n // \\u2500\\u2500\\u2500 Create Batch \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @notice Swap `tokenIn` \\u2192 BZZ via the given path and create a Swarm stamp batch.\\n * @dev `tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\\n * @param path Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\\n * @param maxAmountIn Maximum tokenIn to spend (slippage protection)\\n * @param bzzAmountOut Exact BZZ needed (= swarmBatchTotal = initialBalancePerChunk \\u00d7 2^depth)\\n * @param p Batch creation parameters\\n */\\n function createBatch(\\n bytes calldata path,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut,\\n CreateBatchParams calldata p\\n ) external {\\n address tokenIn = _lastToken(path);\\n uint256 balBefore = IERC20(tokenIn).balanceOf(msg.sender);\\n _swapExactOutput(path, msg.sender, maxAmountIn, bzzAmountOut);\\n uint256 actualAmountIn = balBefore - IERC20(tokenIn).balanceOf(msg.sender);\\n bytes32 batchId = _approveBzzAndCreate(p, bzzAmountOut);\\n emit BatchCreatedViaSwap(batchId, p.owner, tokenIn, actualAmountIn, bzzAmountOut);\\n }\\n\\n /**\\n * @notice Swap native xDAI \\u2192 BZZ and create a Swarm stamp batch.\\n * @dev Send msg.value \\u2265 maxAmountIn. Excess xDAI is refunded.\\n * @param path Exact-output path where the final token MUST be WXDAI:\\n * BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\\n * @param maxAmountIn Maximum xDAI to spend\\n * @param bzzAmountOut Exact BZZ needed\\n * @param p Batch creation parameters\\n */\\n function createBatchNative(\\n bytes calldata path,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut,\\n CreateBatchParams calldata p\\n ) external payable {\\n if (msg.value < maxAmountIn) revert InsufficientNativeValue();\\n IWXDAI(WXDAI).deposit{value: maxAmountIn}();\\n _swapExactOutput(path, address(this), maxAmountIn, bzzAmountOut);\\n uint256 actualAmountIn = maxAmountIn - IERC20(WXDAI).balanceOf(address(this));\\n bytes32 batchId = _approveBzzAndCreate(p, bzzAmountOut);\\n emit BatchCreatedViaSwap(batchId, p.owner, address(0), actualAmountIn, bzzAmountOut);\\n _refundNative();\\n }\\n\\n // \\u2500\\u2500\\u2500 Top Up Batch \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @notice Swap `tokenIn` \\u2192 BZZ and top up an existing Swarm stamp batch.\\n * @dev `tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\\n * @param path Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\\n * @param maxAmountIn Maximum tokenIn to spend\\n * @param bzzAmountOut Exact BZZ needed (= topupAmountPerChunk \\u00d7 2^depth)\\n * @param batchId Batch to top up\\n * @param topupAmountPerChunk Per-chunk top-up amount (matches registry call)\\n */\\n function topUp(\\n bytes calldata path,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut,\\n bytes32 batchId,\\n uint256 topupAmountPerChunk\\n ) external {\\n address tokenIn = _lastToken(path);\\n uint256 balBefore = IERC20(tokenIn).balanceOf(msg.sender);\\n _swapExactOutput(path, msg.sender, maxAmountIn, bzzAmountOut);\\n uint256 actualAmountIn = balBefore - IERC20(tokenIn).balanceOf(msg.sender);\\n _approveBzzAndTopUp(batchId, topupAmountPerChunk, bzzAmountOut);\\n emit BatchToppedUpViaSwap(batchId, tokenIn, actualAmountIn, bzzAmountOut);\\n }\\n\\n /**\\n * @notice Swap native xDAI \\u2192 BZZ and top up an existing Swarm stamp batch.\\n * @dev Send msg.value \\u2265 maxAmountIn. Excess xDAI is refunded.\\n * @param path BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\\n * @param maxAmountIn Maximum xDAI to spend\\n * @param bzzAmountOut Exact BZZ needed\\n * @param batchId Batch to top up\\n * @param topupAmountPerChunk Per-chunk top-up amount\\n */\\n function topUpNative(\\n bytes calldata path,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut,\\n bytes32 batchId,\\n uint256 topupAmountPerChunk\\n ) external payable {\\n if (msg.value < maxAmountIn) revert InsufficientNativeValue();\\n IWXDAI(WXDAI).deposit{value: maxAmountIn}();\\n _swapExactOutput(path, address(this), maxAmountIn, bzzAmountOut);\\n uint256 actualAmountIn = maxAmountIn - IERC20(WXDAI).balanceOf(address(this));\\n _approveBzzAndTopUp(batchId, topupAmountPerChunk, bzzAmountOut);\\n emit BatchToppedUpViaSwap(batchId, address(0), actualAmountIn, bzzAmountOut);\\n _refundNative();\\n }\\n\\n // \\u2500\\u2500\\u2500 Uniswap V3 / SushiSwap V3 Swap Callback \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @notice Called by a SushiSwap V3 pool during swap execution.\\n * @dev Implements the Uniswap V3 callback interface (SushiSwap V3 is compatible).\\n * For multi-hop swaps, this callback chains into the next pool swap before\\n * paying the current pool, routing tokens directly between pools.\\n */\\n function uniswapV3SwapCallback(\\n int256 amount0Delta,\\n int256 amount1Delta,\\n bytes calldata data\\n ) external {\\n if (amount0Delta <= 0 && amount1Delta <= 0) revert InvalidCallback();\\n\\n SwapCallbackData memory cb = abi.decode(data, (SwapCallbackData));\\n\\n // Decode the first pool in the path to verify the caller is legitimate.\\n (address tokenOut, uint24 fee, address tokenIn) = _decodeFirstPool(cb.path);\\n address expectedPool = ISushiV3Factory(SUSHI_FACTORY).getPool(tokenOut, tokenIn, fee);\\n if (msg.sender != expectedPool) revert InvalidCallback();\\n\\n // tokenIn is always what we owe: V3 pools sort tokens by address, so\\n // tokenIn is token0 iff tokenIn < tokenOut (zeroForOne=true \\u2192 amount0Delta > 0).\\n // Either way the positive delta corresponds to tokenIn.\\n address tokenOwed = tokenIn;\\n uint256 amountOwed = uint256(amount0Delta > 0 ? amount0Delta : amount1Delta);\\n\\n if (_hasMultiplePools(cb.path)) {\\n // Multi-hop: continue to next pool. Skip the first token from path to get\\n // the remaining sub-path: mid ++ fee ++ ... ++ tokenIn\\n bytes memory remainingPath = _skipToken(cb.path);\\n\\n // Decode the next pool info from remaining path.\\n (address nextTokenOut, uint24 nextFee, address nextTokenIn) = _decodeFirstPool(remainingPath);\\n address nextPool = ISushiV3Factory(SUSHI_FACTORY).getPool(nextTokenOut, nextTokenIn, nextFee);\\n if (nextPool == address(0)) revert PoolNotFound();\\n\\n // Swap in the next pool, sending output directly to msg.sender (current pool)\\n // so it receives the tokens it needs without going through this contract.\\n bool zeroForOne = nextTokenIn < nextTokenOut;\\n ISushiV3Pool(nextPool).swap(\\n msg.sender, // recipient = current pool (gets tokenOwed directly)\\n zeroForOne,\\n -int256(amountOwed), // exact output = amountOwed\\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\\n abi.encode(SwapCallbackData({\\n path: remainingPath,\\n payer: cb.payer,\\n maxAmountIn: cb.maxAmountIn\\n }))\\n );\\n } else {\\n // Final hop: pay tokenOwed from the original payer.\\n if (amountOwed > cb.maxAmountIn) {\\n revert SlippageExceeded(amountOwed, cb.maxAmountIn);\\n }\\n\\n if (cb.payer == address(this)) {\\n // Native xDAI flow: we already hold WXDAI from the deposit.\\n if (!IERC20(tokenOwed).transfer(msg.sender, amountOwed)) {\\n revert BzzTransferFailed();\\n }\\n } else {\\n // ERC20 flow: pull from user who pre-approved this contract.\\n if (!IERC20(tokenOwed).transferFrom(cb.payer, msg.sender, amountOwed)) {\\n revert BzzTransferFailed();\\n }\\n }\\n }\\n }\\n\\n // \\u2500\\u2500\\u2500 Internal Helpers \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @dev Execute an exact-output swap for `bzzAmountOut` BZZ using the given path.\\n * The path is in exactOutput encoding: BZZ ++ fee ++ [...] ++ tokenIn.\\n * BZZ lands in address(this) after the swap completes.\\n */\\n function _swapExactOutput(\\n bytes memory path,\\n address payer,\\n uint256 maxAmountIn,\\n uint256 bzzAmountOut\\n ) internal {\\n if (path.length < POP_OFFSET) revert InvalidPath();\\n\\n // Decode the first (and for single-hop, only) pool in the path.\\n (address tokenOut, uint24 fee, address tokenIn) = _decodeFirstPool(path);\\n if (tokenOut != BZZ) revert InvalidPath();\\n\\n address pool = ISushiV3Factory(SUSHI_FACTORY).getPool(tokenOut, tokenIn, fee);\\n if (pool == address(0)) revert PoolNotFound();\\n\\n // zeroForOne: true if tokenIn is token0 (address < BZZ)\\n bool zeroForOne = tokenIn < tokenOut;\\n\\n // amountSpecified < 0 \\u2192 exact output (we want exactly bzzAmountOut of BZZ)\\n ISushiV3Pool(pool).swap(\\n address(this), // receive BZZ here\\n zeroForOne,\\n -int256(bzzAmountOut),\\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\\n abi.encode(SwapCallbackData({\\n path: path,\\n payer: payer,\\n maxAmountIn: maxAmountIn\\n }))\\n );\\n }\\n\\n /**\\n * @dev Approve BZZ to the stamps registry and call createBatchRegistry.\\n * Returns the keccak256 batch ID consistent with the registry's derivation.\\n */\\n function _approveBzzAndCreate(\\n CreateBatchParams memory p,\\n uint256 bzzAmountOut\\n ) internal returns (bytes32 batchId) {\\n if (!IERC20(BZZ).approve(address(stampsRegistry), bzzAmountOut)) {\\n revert BzzApproveFailed();\\n }\\n\\n stampsRegistry.createBatchRegistry(\\n p.owner,\\n p.nodeAddress,\\n p.initialBalancePerChunk,\\n p.depth,\\n p.bucketDepth,\\n p.nonce,\\n p.immutable_\\n );\\n\\n // Registry derives batchId as keccak256(abi.encode(registry, nonce)).\\n batchId = keccak256(abi.encode(address(stampsRegistry), p.nonce));\\n }\\n\\n /**\\n * @dev Approve BZZ to the stamps registry and call topUpBatch.\\n */\\n function _approveBzzAndTopUp(\\n bytes32 batchId,\\n uint256 topupAmountPerChunk,\\n uint256 bzzAmountOut\\n ) internal {\\n if (!IERC20(BZZ).approve(address(stampsRegistry), bzzAmountOut)) {\\n revert BzzApproveFailed();\\n }\\n stampsRegistry.topUpBatch(batchId, topupAmountPerChunk);\\n }\\n\\n /**\\n * @dev Unwrap any remaining WXDAI and refund all native xDAI to msg.sender.\\n */\\n function _refundNative() internal {\\n uint256 wxdaiBalance = IERC20(WXDAI).balanceOf(address(this));\\n if (wxdaiBalance > 0) {\\n IWXDAI(WXDAI).withdraw(wxdaiBalance);\\n }\\n uint256 nativeBalance = address(this).balance;\\n if (nativeBalance > 0) {\\n (bool ok,) = msg.sender.call{value: nativeBalance}(\\\"\\\");\\n if (!ok) revert NativeRefundFailed();\\n }\\n }\\n\\n // \\u2500\\u2500\\u2500 Path Utilities \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\n\\n /**\\n * @dev Returns true if the path encodes more than one pool (length > 43 bytes).\\n */\\n function _hasMultiplePools(bytes memory path) internal pure returns (bool) {\\n return path.length > POP_OFFSET;\\n }\\n\\n /**\\n * @dev Decodes the first pool segment from the path:\\n * tokenA (20 bytes) ++ fee (3 bytes) ++ tokenB (20 bytes)\\n */\\n function _decodeFirstPool(bytes memory path)\\n internal\\n pure\\n returns (address tokenA, uint24 fee, address tokenB)\\n {\\n tokenA = _toAddress(path, 0);\\n fee = _toUint24(path, ADDR_SIZE);\\n tokenB = _toAddress(path, NEXT_OFFSET);\\n }\\n\\n /**\\n * @dev Returns the path with the first token removed (skips ADDR_SIZE + FEE_SIZE bytes).\\n * Used to advance through multi-hop paths in the callback.\\n */\\n function _skipToken(bytes memory path) internal pure returns (bytes memory skipped) {\\n uint256 newLen = path.length - NEXT_OFFSET;\\n skipped = new bytes(newLen);\\n assembly {\\n let src := add(add(path, 0x20), NEXT_OFFSET)\\n let dst := add(skipped, 0x20)\\n // Copy 32 bytes at a time; the last chunk may write up to 31 bytes past\\n // newLen, but into the 32-byte-aligned padding that `new bytes` allocates.\\n for { let i := 0 } lt(i, newLen) { i := add(i, 32) } {\\n mstore(add(dst, i), mload(add(src, i)))\\n }\\n }\\n }\\n\\n /**\\n * @dev Extracts the last 20-byte address from the path (the tokenIn address).\\n */\\n function _lastToken(bytes memory path) internal pure returns (address token) {\\n uint256 offset = path.length - ADDR_SIZE;\\n token = _toAddress(path, offset);\\n }\\n\\n /**\\n * @dev Reads a 20-byte address from `data` at `offset` using assembly.\\n * The address occupies bytes [offset, offset+20) and is right-aligned\\n * by shifting the 32-byte word 96 bits right.\\n */\\n function _toAddress(bytes memory data, uint256 offset) internal pure returns (address addr) {\\n assembly {\\n addr := shr(96, mload(add(add(data, 0x20), offset)))\\n }\\n }\\n\\n /**\\n * @dev Reads a 3-byte uint24 from `data` at `offset` using assembly.\\n * Shifts the 32-byte word 232 bits right to extract the top 3 bytes.\\n */\\n function _toUint24(bytes memory data, uint256 offset) internal pure returns (uint24 result) {\\n assembly {\\n result := shr(232, mload(add(add(data, 0x20), offset)))\\n }\\n }\\n}\\n\",\"keccak256\":\"0xd9a4ea9d247ba720ee207162c8f62576848ddae284038e68c649384d1d3d3547\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60a0346100b557601f611a1038819003918201601f19168301916001600160401b038311848410176100ba578084926020946040528339810103126100b557516001600160a01b038116908190036100b55780156100805760805260405161193f90816100d18239608051818181610733015281816114fb01526117170152f35b60405162461bcd60e51b815260206004820152600d60248201526c7a65726f20726567697374727960981b6044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c80630e70205d146100db5780630f43a7df146100d65780632a426602146100d15780632cb1dd93146100cc578063348fdcc4146100c7578063604a52fb146100c25780638acc27b9146100bd578063a7c086be146100b8578063b753bb47146100b3578063b972d70d146100ae578063eaa4f401146100a95763fa461e330361000e576109cc565b61099d565b6108d1565b6108a2565b610762565b61071d565b6106ee565b6105e2565b61045c565b610308565b61018a565b6100f0565b60009103126100eb57565b600080fd5b346100eb5760003660031901126100eb57602060405173dbf3ea6f5bee45c02255b2c26a16f300502f68da8152f35b9181601f840112156100eb578235916001600160401b0383116100eb57602083818601950101116100eb57565b60a06003198201126100eb57600435906001600160401b0382116100eb576101769160040161011f565b909160243590604435906064359060843590565b346100eb576101983661014c565b929094916101af6101aa368786610ed0565b6112f2565b6040516370a0823160e01b80825233600483015260209792956001600160a01b038716959093909290919089816024818a5afa9384156102c9578a9387926000966102ce575b5061020961020e9495969733923691610ed0565b61130a565b60405190815233600482015293849060249082905afa9283156102c957610269610295946102709385937fc65f0fa5466fb164f60e47c03216e4cae4cf47cb57de7d15f9cbdd413028ace69a60009361029a575b5050610f38565b95886114e4565b60405193849384604091949392606082019560018060a01b0316825260208201520152565b0390a2005b6102ba929350803d106102c2575b6102b28183610e66565b810190610f07565b903880610262565b503d6102a8565b610f16565b8596506102ee6102099161020e96973d8a116102c2576102b28183610e66565b969594506101f5565b6001600160a01b038116036100eb57565b346100eb5760603660031901126100eb57600435610325816102f7565b60243562ffffff811681036100eb576103e891610386608092610358610349610e87565b6001600160a01b039094168452565b73dbf3ea6f5bee45c02255b2c26a16f300502f68da6020840152604435604084015262ffffff166060830152565b60008183015260408051635e90b82560e11b815282516001600160a01b039081166004830152602084015181166024830152918301516044820152606083015162ffffff166064820152608090920151166084820152918290819060a4820190565b0381600073b1e835dc2785b52265711e17fccb0fd018226a6e5af180156102c9576104269160009161042a575b506040519081529081906020820190565b0390f35b61044c915060803d608011610455575b6104448183610e66565b810190610f5b565b50505038610415565b503d61043a565b6104653661014c565b91929094938134106105895773e91d153e0b41518a2ce8dd3d7944fa863463a97d94853b156100eb57604051630d0e30db60e41b8152600081600481878b5af19283156102c957610209879386936104c696610570575b5030923691610ed0565b6040516370a0823160e01b815230600482015293602090859060249082905afa9081156102c95761052661052d9285927fc65f0fa5466fb164f60e47c03216e4cae4cf47cb57de7d15f9cbdd413028ace69760009261054f575b50610f38565b92866114e4565b60408051600081526020810192909252810191909152606090a2610019611614565b61056991925060203d6020116102c2576102b28183610e66565b9038610520565b8061057d61058392610e33565b806100e0565b386104bc565b604051631ac4c73760e11b8152600490fd5b6101406003198201126100eb576004356001600160401b0381116100eb57816105c69160040161011f565b929092916024359160e06044359260631901126100eb57606490565b6105eb3661059b565b928294919434106105895773e91d153e0b41518a2ce8dd3d7944fa863463a97d91823b156100eb57604051630d0e30db60e41b815260008160048188885af19283156102c9576102098893879361064a96610570575030923691610ed0565b6040516370a0823160e01b815230600482015290602090829060249082905afa80156102c9577f1ec8178b486abb6332d45b8822f76f29e264fc077dc6ae1d5db41f9a1f2be645926106a39260009261054f5750610f38565b926106b7816106b23686610fb0565b611700565b926001600160a01b03906106ca90611041565b604080516000815260208101979097528601929092521692606090a3610019611614565b346100eb5760003660031901126100eb57602060405173e91d153e0b41518a2ce8dd3d7944fa863463a97d8152f35b346100eb5760003660031901126100eb576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100eb576107703661059b565b929093916107826101aa368486610ed0565b6040516370a0823160e01b8082523360048301526001600160a01b039492938486169360209391908483602481895afa9889156102c9578b9360009a610871575b5091859493916102096107d99433923691610ed0565b60405190815233600482015292839060249082905afa80156102c9577f1ec8178b486abb6332d45b8822f76f29e264fc077dc6ae1d5db41f9a1f2be6459561086c9361082d9360009361029a575050610f38565b9261084561083f886106b2368a610fb0565b96611041565b169560405193849384604091949392606082019560018060a01b0316825260208201520152565b0390a3005b6107d993919a509161020961089588979694883d8a116102c2576102b28183610e66565b9b929450509193946107c3565b346100eb5760003660031901126100eb57602060405173f78031cbca409f2fb6876bdfdbc1b2df24cf9bef8152f35b346100eb5760403660031901126100eb576004356001600160401b0381116100eb576000610905606492369060040161011f565b9283916040519485938492632f80bb1d60e01b84526040600485015281604485015284840137848382840101526024356024830152601f801991011681010301818373b1e835dc2785b52265711e17fccb0fd018226a6e5af180156102c9576104269160009161098057506040519081529081906020820190565b61044c91503d806000833e6109958183610e66565b8101906110ce565b346100eb5760003660031901126100eb57602060405173b1e835dc2785b52265711e17fccb0fd018226a6e8152f35b346100eb5760603660031901126100eb57600460243581356044356001600160401b0381116100eb57610a02903690850161011f565b6000949185841391821580610e13575b610e025790610a2391810190611174565b91610a458351602081015160601c916037603483015160e81c92015160601c90565b9291939060409687519889610a8e630b4c774160e11b948583528860209a89850191939262ffffff90604092606085019660018060a01b03809216865216602085015216910152565b0393878b73f78031cbca409f2fb6876bdfdbc1b2df24cf9bef9681885afa9a8b156102c9578c9b610de3575b506001600160a01b039a8b163303610dd35715610dcb5750935b865151602b1015610c9c57508697869786610af3610b509899516118a1565b94610b1486602081015160601c916037603483015160e81c92015160601c90565b94519687526001600160a01b038083168a8901908152908616602082015262ffffff909116604082015290999395938492918391829160600190565b03915afa80156102c95782918c91610c6f575b5016958615610c5f5792610bea610b8a8b999897969484610c06958f98169116109661121a565b978615610c44576401000276a49a5b81810151908b015190610bc9906001600160a01b0316610bb7610ea6565b9586526001600160a01b031683860152565b8a840152610bdc8a51938492830161126b565b03601f198101835282610e66565b8751630251596160e31b815298899788968795339087016112b9565b03925af180156102c957610c1957505080f35b81610c3892903d10610c3d575b610c308183610e66565b8101906112a3565b505080f35b503d610c26565b73fffd8963efd1fc6a506488495d951d5263988d259a610b99565b89516301dbb3ff60e61b81528590fd5b610c8f9150893d8b11610c95575b610c878183610e66565b8101906111f0565b38610b63565b503d610c7d565b93929794958092508791500151808311610da7575084015184929190879089906001600160a01b03168087163003610d4f5750875163a9059cbb60e01b8152338a820190815260208101949094529586949385935083906040010393165af19182156102c9578592610d22575b505015610d1557505080f35b516304aa965560e41b8152fd5b610d419250803d10610d48575b610d398183610e66565b810190611205565b3880610d09565b503d610d2f565b88516323b872dd60e01b81526001600160a01b0390911692810192835233602084015260408301939093529194859392849290919083906060010393165af19182156102c9578592610d2257505015610d1557505080f35b86516371c4efed60e01b815280890184815260208101929092529081906040010390fd5b905093610ad4565b895163f7a632f560e01b81528690fd5b610dfb919b50883d8a11610c9557610c878183610e66565b9938610aba565b60405163f7a632f560e01b81528490fd5b5086861315610a12565b634e487b7160e01b600052604160045260246000fd5b6001600160401b038111610e4657604052565b610e1d565b606081019081106001600160401b03821117610e4657604052565b90601f801991011681019081106001600160401b03821117610e4657604052565b6040519060a082018281106001600160401b03821117610e4657604052565b60405190610eb382610e4b565b565b6001600160401b038111610e4657601f01601f191660200190565b929192610edc82610eb5565b91610eea6040519384610e66565b8294818452818301116100eb578281602093846000960137010152565b908160209103126100eb575190565b6040513d6000823e3d90fd5b634e487b7160e01b600052601160045260246000fd5b91908203918211610f4557565b610f22565b519063ffffffff821682036100eb57565b91908260809103126100eb578151916020810151610f78816102f7565b916060610f8760408401610f4a565b92015190565b359060ff821682036100eb57565b801515036100eb57565b3590610eb382610f9b565b91908260e09103126100eb5760405160e081018181106001600160401b03821117610e465760405260c061103c8183958035610feb816102f7565b85526020810135610ffb816102f7565b60208601526040810135604086015261101660608201610f8d565b606086015261102760808201610f8d565b608086015260a081013560a086015201610fa5565b910152565b3561104b816102f7565b90565b6001600160401b038111610e465760051b60200190565b9080601f830112156100eb578151906020916110808161104e565b9361108e6040519586610e66565b81855260208086019260051b8201019283116100eb57602001905b8282106110b7575050505090565b8380916110c384610f4a565b8152019101906110a9565b6080818303126100eb5780519260209283830151936001600160401b03948581116100eb5784019082601f830112156100eb5781519161110d8361104e565b9261111b6040519485610e66565b808452828085019160051b830101918583116100eb578301905b82821061115b57505050509360408401519081116100eb57606091610f87918501611065565b8380918351611169816102f7565b815201910190611135565b906020828203126100eb5781356001600160401b03928382116100eb57016060818303126100eb57604051926111a984610e4b565b81359081116100eb57810182601f820112156100eb576040928160206111d193359101610ed0565b835260208101356111e1816102f7565b60208401520135604082015290565b908160209103126100eb575161104b816102f7565b908160209103126100eb575161104b81610f9b565b600160ff1b8114610f455760000390565b919082519283825260005b848110611257575050826000602080949584010152601f8019910116010190565b602081830181015184830182015201611236565b60208152606060406112888451836020860152608085019061122b565b60208501516001600160a01b03168483015293015191015290565b91908260409103126100eb576020825192015190565b6001600160a01b039182168152911515602083015260408201929092529116606082015260a06080820181905261104b9291019061122b565b805180601319810111610f455701600c015160601c90565b929091602b8451106114d25761133684602081015160601c916037603483015160e81c92015160601c90565b9195919391906001600160a01b038088169173dbf3ea6f5bee45c02255b2c26a16f300502f68d91983016114d25760408051630b4c774160e11b81526001600160a01b039a8b16600482015299881660248b015262ffffff9190911660448a01529760208160648173f78031cbca409f2fb6876bdfdbc1b2df24cf9bef5afa80156102c95782916000916114b3575b50169586156114a25761142a9795936113ea6000948b9997946114389416109361121a565b94838514611484576114186401000276a4985b611405610ea6565b9384526001600160a01b03166020840152565b8882015287519889916020830161126b565b03601f198101895288610e66565b611458865197889687958694630251596160e31b865230600487016112b9565b03925af180156102c95761146a575050565b8161148092903d10610c3d57610c308183610e66565b5050565b61141873fffd8963efd1fc6a506488495d951d5263988d25986113fd565b88516301dbb3ff60e61b8152600490fd5b6114cc915060203d602011610c9557610c878183610e66565b386113c5565b6040516320db826760e01b8152600490fd5b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482018190526024820194909452909392600092916020816044818773dbf3ea6f5bee45c02255b2c26a16f300502f68da5af19081156102c95784916115c5575b50156115b357803b156115af57604051631549361960e01b8152600481019590955260248501919091529192918290604490829084905af180156102c9576115a25750565b8061057d610eb392610e33565b8280fd5b604051632546bed360e01b8152600490fd5b6115de915060203d602011610d4857610d398183610e66565b3861155d565b3d1561160f573d906115f582610eb5565b916116036040519384610e66565b82523d6000602084013e565b606090565b6040516370a0823160e01b815230600482015273e91d153e0b41518a2ce8dd3d7944fa863463a97d602082602481845afa9182156102c9576000926116df575b5081611691575b505047806116665750565b600080808093335af16116776115e4565b501561167f57565b6040516308520d7160e41b8152600490fd5b803b156100eb57604051632e1a7d4d60e01b815260048101929092526000908290602490829084905af180156102c9576116cc575b8061165b565b8061057d6116d992610e33565b386116c6565b6116f991925060203d6020116102c2576102b28183610e66565b9038611654565b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482018190526024820193909352600091906020816044818673dbf3ea6f5bee45c02255b2c26a16f300502f68da5af19081156102c9578391611882575b50156115b35780516001600160a01b031660208201519092906001600160a01b03166040830151936117ac606085015160ff1690565b608085015160ff169460a08101956117c960c08851930151151590565b92893b1561187e576040516317f8c86b60e11b81526001600160a01b039586166004820152959094166024860152604485019790975260ff928316606485015291909516608483015260a482015292151560c48401528260e48183875af19081156102c957610bdc926118659261186b575b5051604080516001600160a01b039095166020860190815290850191909152929182906060820190565b51902090565b8061057d61187892610e33565b3861183b565b8680fd5b61189b915060203d602011610d4857610d398183610e66565b38611776565b805190916016198201918211610f45576118ba82610eb5565b916118c86040519384610e66565b8083526118d481610eb5565b60209190601f190136858401378360005b8281106118f55750505050909150565b8681016037015182820185015283016118e556fea26469706673582212208e898522f172d776718061f168529fda04f44aee90ca8b1d2114da180ee6b95164736f6c63430008170033", + "deployedBytecode": "0x6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c80630e70205d146100db5780630f43a7df146100d65780632a426602146100d15780632cb1dd93146100cc578063348fdcc4146100c7578063604a52fb146100c25780638acc27b9146100bd578063a7c086be146100b8578063b753bb47146100b3578063b972d70d146100ae578063eaa4f401146100a95763fa461e330361000e576109cc565b61099d565b6108d1565b6108a2565b610762565b61071d565b6106ee565b6105e2565b61045c565b610308565b61018a565b6100f0565b60009103126100eb57565b600080fd5b346100eb5760003660031901126100eb57602060405173dbf3ea6f5bee45c02255b2c26a16f300502f68da8152f35b9181601f840112156100eb578235916001600160401b0383116100eb57602083818601950101116100eb57565b60a06003198201126100eb57600435906001600160401b0382116100eb576101769160040161011f565b909160243590604435906064359060843590565b346100eb576101983661014c565b929094916101af6101aa368786610ed0565b6112f2565b6040516370a0823160e01b80825233600483015260209792956001600160a01b038716959093909290919089816024818a5afa9384156102c9578a9387926000966102ce575b5061020961020e9495969733923691610ed0565b61130a565b60405190815233600482015293849060249082905afa9283156102c957610269610295946102709385937fc65f0fa5466fb164f60e47c03216e4cae4cf47cb57de7d15f9cbdd413028ace69a60009361029a575b5050610f38565b95886114e4565b60405193849384604091949392606082019560018060a01b0316825260208201520152565b0390a2005b6102ba929350803d106102c2575b6102b28183610e66565b810190610f07565b903880610262565b503d6102a8565b610f16565b8596506102ee6102099161020e96973d8a116102c2576102b28183610e66565b969594506101f5565b6001600160a01b038116036100eb57565b346100eb5760603660031901126100eb57600435610325816102f7565b60243562ffffff811681036100eb576103e891610386608092610358610349610e87565b6001600160a01b039094168452565b73dbf3ea6f5bee45c02255b2c26a16f300502f68da6020840152604435604084015262ffffff166060830152565b60008183015260408051635e90b82560e11b815282516001600160a01b039081166004830152602084015181166024830152918301516044820152606083015162ffffff166064820152608090920151166084820152918290819060a4820190565b0381600073b1e835dc2785b52265711e17fccb0fd018226a6e5af180156102c9576104269160009161042a575b506040519081529081906020820190565b0390f35b61044c915060803d608011610455575b6104448183610e66565b810190610f5b565b50505038610415565b503d61043a565b6104653661014c565b91929094938134106105895773e91d153e0b41518a2ce8dd3d7944fa863463a97d94853b156100eb57604051630d0e30db60e41b8152600081600481878b5af19283156102c957610209879386936104c696610570575b5030923691610ed0565b6040516370a0823160e01b815230600482015293602090859060249082905afa9081156102c95761052661052d9285927fc65f0fa5466fb164f60e47c03216e4cae4cf47cb57de7d15f9cbdd413028ace69760009261054f575b50610f38565b92866114e4565b60408051600081526020810192909252810191909152606090a2610019611614565b61056991925060203d6020116102c2576102b28183610e66565b9038610520565b8061057d61058392610e33565b806100e0565b386104bc565b604051631ac4c73760e11b8152600490fd5b6101406003198201126100eb576004356001600160401b0381116100eb57816105c69160040161011f565b929092916024359160e06044359260631901126100eb57606490565b6105eb3661059b565b928294919434106105895773e91d153e0b41518a2ce8dd3d7944fa863463a97d91823b156100eb57604051630d0e30db60e41b815260008160048188885af19283156102c9576102098893879361064a96610570575030923691610ed0565b6040516370a0823160e01b815230600482015290602090829060249082905afa80156102c9577f1ec8178b486abb6332d45b8822f76f29e264fc077dc6ae1d5db41f9a1f2be645926106a39260009261054f5750610f38565b926106b7816106b23686610fb0565b611700565b926001600160a01b03906106ca90611041565b604080516000815260208101979097528601929092521692606090a3610019611614565b346100eb5760003660031901126100eb57602060405173e91d153e0b41518a2ce8dd3d7944fa863463a97d8152f35b346100eb5760003660031901126100eb576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346100eb576107703661059b565b929093916107826101aa368486610ed0565b6040516370a0823160e01b8082523360048301526001600160a01b039492938486169360209391908483602481895afa9889156102c9578b9360009a610871575b5091859493916102096107d99433923691610ed0565b60405190815233600482015292839060249082905afa80156102c9577f1ec8178b486abb6332d45b8822f76f29e264fc077dc6ae1d5db41f9a1f2be6459561086c9361082d9360009361029a575050610f38565b9261084561083f886106b2368a610fb0565b96611041565b169560405193849384604091949392606082019560018060a01b0316825260208201520152565b0390a3005b6107d993919a509161020961089588979694883d8a116102c2576102b28183610e66565b9b929450509193946107c3565b346100eb5760003660031901126100eb57602060405173f78031cbca409f2fb6876bdfdbc1b2df24cf9bef8152f35b346100eb5760403660031901126100eb576004356001600160401b0381116100eb576000610905606492369060040161011f565b9283916040519485938492632f80bb1d60e01b84526040600485015281604485015284840137848382840101526024356024830152601f801991011681010301818373b1e835dc2785b52265711e17fccb0fd018226a6e5af180156102c9576104269160009161098057506040519081529081906020820190565b61044c91503d806000833e6109958183610e66565b8101906110ce565b346100eb5760003660031901126100eb57602060405173b1e835dc2785b52265711e17fccb0fd018226a6e8152f35b346100eb5760603660031901126100eb57600460243581356044356001600160401b0381116100eb57610a02903690850161011f565b6000949185841391821580610e13575b610e025790610a2391810190611174565b91610a458351602081015160601c916037603483015160e81c92015160601c90565b9291939060409687519889610a8e630b4c774160e11b948583528860209a89850191939262ffffff90604092606085019660018060a01b03809216865216602085015216910152565b0393878b73f78031cbca409f2fb6876bdfdbc1b2df24cf9bef9681885afa9a8b156102c9578c9b610de3575b506001600160a01b039a8b163303610dd35715610dcb5750935b865151602b1015610c9c57508697869786610af3610b509899516118a1565b94610b1486602081015160601c916037603483015160e81c92015160601c90565b94519687526001600160a01b038083168a8901908152908616602082015262ffffff909116604082015290999395938492918391829160600190565b03915afa80156102c95782918c91610c6f575b5016958615610c5f5792610bea610b8a8b999897969484610c06958f98169116109661121a565b978615610c44576401000276a49a5b81810151908b015190610bc9906001600160a01b0316610bb7610ea6565b9586526001600160a01b031683860152565b8a840152610bdc8a51938492830161126b565b03601f198101835282610e66565b8751630251596160e31b815298899788968795339087016112b9565b03925af180156102c957610c1957505080f35b81610c3892903d10610c3d575b610c308183610e66565b8101906112a3565b505080f35b503d610c26565b73fffd8963efd1fc6a506488495d951d5263988d259a610b99565b89516301dbb3ff60e61b81528590fd5b610c8f9150893d8b11610c95575b610c878183610e66565b8101906111f0565b38610b63565b503d610c7d565b93929794958092508791500151808311610da7575084015184929190879089906001600160a01b03168087163003610d4f5750875163a9059cbb60e01b8152338a820190815260208101949094529586949385935083906040010393165af19182156102c9578592610d22575b505015610d1557505080f35b516304aa965560e41b8152fd5b610d419250803d10610d48575b610d398183610e66565b810190611205565b3880610d09565b503d610d2f565b88516323b872dd60e01b81526001600160a01b0390911692810192835233602084015260408301939093529194859392849290919083906060010393165af19182156102c9578592610d2257505015610d1557505080f35b86516371c4efed60e01b815280890184815260208101929092529081906040010390fd5b905093610ad4565b895163f7a632f560e01b81528690fd5b610dfb919b50883d8a11610c9557610c878183610e66565b9938610aba565b60405163f7a632f560e01b81528490fd5b5086861315610a12565b634e487b7160e01b600052604160045260246000fd5b6001600160401b038111610e4657604052565b610e1d565b606081019081106001600160401b03821117610e4657604052565b90601f801991011681019081106001600160401b03821117610e4657604052565b6040519060a082018281106001600160401b03821117610e4657604052565b60405190610eb382610e4b565b565b6001600160401b038111610e4657601f01601f191660200190565b929192610edc82610eb5565b91610eea6040519384610e66565b8294818452818301116100eb578281602093846000960137010152565b908160209103126100eb575190565b6040513d6000823e3d90fd5b634e487b7160e01b600052601160045260246000fd5b91908203918211610f4557565b610f22565b519063ffffffff821682036100eb57565b91908260809103126100eb578151916020810151610f78816102f7565b916060610f8760408401610f4a565b92015190565b359060ff821682036100eb57565b801515036100eb57565b3590610eb382610f9b565b91908260e09103126100eb5760405160e081018181106001600160401b03821117610e465760405260c061103c8183958035610feb816102f7565b85526020810135610ffb816102f7565b60208601526040810135604086015261101660608201610f8d565b606086015261102760808201610f8d565b608086015260a081013560a086015201610fa5565b910152565b3561104b816102f7565b90565b6001600160401b038111610e465760051b60200190565b9080601f830112156100eb578151906020916110808161104e565b9361108e6040519586610e66565b81855260208086019260051b8201019283116100eb57602001905b8282106110b7575050505090565b8380916110c384610f4a565b8152019101906110a9565b6080818303126100eb5780519260209283830151936001600160401b03948581116100eb5784019082601f830112156100eb5781519161110d8361104e565b9261111b6040519485610e66565b808452828085019160051b830101918583116100eb578301905b82821061115b57505050509360408401519081116100eb57606091610f87918501611065565b8380918351611169816102f7565b815201910190611135565b906020828203126100eb5781356001600160401b03928382116100eb57016060818303126100eb57604051926111a984610e4b565b81359081116100eb57810182601f820112156100eb576040928160206111d193359101610ed0565b835260208101356111e1816102f7565b60208401520135604082015290565b908160209103126100eb575161104b816102f7565b908160209103126100eb575161104b81610f9b565b600160ff1b8114610f455760000390565b919082519283825260005b848110611257575050826000602080949584010152601f8019910116010190565b602081830181015184830182015201611236565b60208152606060406112888451836020860152608085019061122b565b60208501516001600160a01b03168483015293015191015290565b91908260409103126100eb576020825192015190565b6001600160a01b039182168152911515602083015260408201929092529116606082015260a06080820181905261104b9291019061122b565b805180601319810111610f455701600c015160601c90565b929091602b8451106114d25761133684602081015160601c916037603483015160e81c92015160601c90565b9195919391906001600160a01b038088169173dbf3ea6f5bee45c02255b2c26a16f300502f68d91983016114d25760408051630b4c774160e11b81526001600160a01b039a8b16600482015299881660248b015262ffffff9190911660448a01529760208160648173f78031cbca409f2fb6876bdfdbc1b2df24cf9bef5afa80156102c95782916000916114b3575b50169586156114a25761142a9795936113ea6000948b9997946114389416109361121a565b94838514611484576114186401000276a4985b611405610ea6565b9384526001600160a01b03166020840152565b8882015287519889916020830161126b565b03601f198101895288610e66565b611458865197889687958694630251596160e31b865230600487016112b9565b03925af180156102c95761146a575050565b8161148092903d10610c3d57610c308183610e66565b5050565b61141873fffd8963efd1fc6a506488495d951d5263988d25986113fd565b88516301dbb3ff60e61b8152600490fd5b6114cc915060203d602011610c9557610c878183610e66565b386113c5565b6040516320db826760e01b8152600490fd5b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482018190526024820194909452909392600092916020816044818773dbf3ea6f5bee45c02255b2c26a16f300502f68da5af19081156102c95784916115c5575b50156115b357803b156115af57604051631549361960e01b8152600481019590955260248501919091529192918290604490829084905af180156102c9576115a25750565b8061057d610eb392610e33565b8280fd5b604051632546bed360e01b8152600490fd5b6115de915060203d602011610d4857610d398183610e66565b3861155d565b3d1561160f573d906115f582610eb5565b916116036040519384610e66565b82523d6000602084013e565b606090565b6040516370a0823160e01b815230600482015273e91d153e0b41518a2ce8dd3d7944fa863463a97d602082602481845afa9182156102c9576000926116df575b5081611691575b505047806116665750565b600080808093335af16116776115e4565b501561167f57565b6040516308520d7160e41b8152600490fd5b803b156100eb57604051632e1a7d4d60e01b815260048101929092526000908290602490829084905af180156102c9576116cc575b8061165b565b8061057d6116d992610e33565b386116c6565b6116f991925060203d6020116102c2576102b28183610e66565b9038611654565b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016600482018190526024820193909352600091906020816044818673dbf3ea6f5bee45c02255b2c26a16f300502f68da5af19081156102c9578391611882575b50156115b35780516001600160a01b031660208201519092906001600160a01b03166040830151936117ac606085015160ff1690565b608085015160ff169460a08101956117c960c08851930151151590565b92893b1561187e576040516317f8c86b60e11b81526001600160a01b039586166004820152959094166024860152604485019790975260ff928316606485015291909516608483015260a482015292151560c48401528260e48183875af19081156102c957610bdc926118659261186b575b5051604080516001600160a01b039095166020860190815290850191909152929182906060820190565b51902090565b8061057d61187892610e33565b3861183b565b8680fd5b61189b915060203d602011610d4857610d398183610e66565b38611776565b805190916016198201918211610f45576118ba82610eb5565b916118c86040519384610e66565b8083526118d481610eb5565b60209190601f190136858401378360005b8281106118f55750505050909150565b8681016037015182820185015283016118e556fea26469706673582212208e898522f172d776718061f168529fda04f44aee90ca8b1d2114da180ee6b95164736f6c63430008170033", "devdoc": { "kind": "dev", "methods": { diff --git a/src/app/components/constants.ts b/src/app/components/constants.ts index d5d2ddc..415623a 100644 --- a/src/app/components/constants.ts +++ b/src/app/components/constants.ts @@ -307,7 +307,7 @@ export const SUSHI_QUOTER_ADDRESS = */ export const SUSHI_STAMPS_ROUTER_ADDRESS = process.env.NEXT_PUBLIC_SUSHI_STAMPS_ROUTER_ADDRESS || - '0x2a0a54368Bb6b0D8fa31568D092ffBDf350ab553'; + '0xf244cC25EAD03a99de8B407A3237aaf54D1b779C'; /** Minimal ABI for the SushiSwap V3 Factory – only what we need for pool discovery */ export const SUSHI_FACTORY_ABI = [ From 6982bb61560c7222e68fb21d6c9babf07f4113aa Mon Sep 17 00:00:00 2001 From: Cardinal Date: Wed, 8 Apr 2026 21:32:00 +0200 Subject: [PATCH 2/2] add deploy info --- .../255f96c59429e1e56480af0cdaa74e3a.json | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 deployments/gnosis/solcInputs/255f96c59429e1e56480af0cdaa74e3a.json diff --git a/deployments/gnosis/solcInputs/255f96c59429e1e56480af0cdaa74e3a.json b/deployments/gnosis/solcInputs/255f96c59429e1e56480af0cdaa74e3a.json new file mode 100644 index 0000000..cee9b3e --- /dev/null +++ b/deployments/gnosis/solcInputs/255f96c59429e1e56480af0cdaa74e3a.json @@ -0,0 +1,37 @@ +{ + "language": "Solidity", + "sources": { + "contracts/SushiSwapStampsRouter.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.23;\n\n/*\n ███████╗██╗ ██╗███████╗██╗ ██╗██╗███████╗██╗ ██╗ █████╗ ██████╗\n ██╔════╝██║ ██║██╔════╝██║ ██║██║██╔════╝██║ ██║██╔══██╗██╔══██╗\n ███████╗██║ ██║███████╗███████║██║███████╗██║ █╗ ██║███████║██████╔╝\n ╚════██║██║ ██║╚════██║██╔══██║██║╚════██║██║███╗██║██╔══██║██╔═══╝\n ███████║╚██████╔╝███████║██║ ██║██║███████║╚███╔███╔╝██║ ██║██║\n ╚══════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝\n\n ███████╗████████╗ █████╗ ███╗ ███╗██████╗ ███████╗\n ██╔════╝╚══██╔══╝██╔══██╗████╗ ████║██╔══██╗██╔════╝\n ███████╗ ██║ ███████║██╔████╔██║██████╔╝███████╗\n ╚════██║ ██║ ██╔══██║██║╚██╔╝██║██╔═══╝ ╚════██║\n ███████║ ██║ ██║ ██║██║ ╚═╝ ██║██║ ███████║\n ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝\n\n ██████╗ ██████╗ ██╗ ██╗████████╗███████╗██████╗\n ██╔══██╗██╔═══██╗██║ ██║╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝██║ ██║██║ ██║ ██║ █████╗ ██████╔╝\n ██╔══██╗██║ ██║██║ ██║ ██║ ██╔══╝ ██╔══██╗\n ██║ ██║╚██████╔╝╚██████╔╝ ██║ ███████╗██║ ██║\n ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝\n*/\n\n/**\n * @title SushiSwapStampsRouter\n * @notice Swaps any Gnosis-chain token to BZZ via SushiSwap V3 and atomically\n * creates or tops up a Swarm postage-stamp batch in a single transaction.\n *\n * @dev Implements the Uniswap V3 callback interface (SushiSwap V3 is fully compatible).\n * Supports both single-hop and multi-hop exact-output swaps via path encoding.\n *\n * Path encoding for exactOutput swaps (reversed token order):\n * single-hop: BZZ ++ uint24(fee) ++ tokenIn (43 bytes)\n * two-hop: BZZ ++ uint24(fee2) ++ mid ++ uint24(fee1) ++ tokenIn (66 bytes)\n *\n * Quote functions are non-view (Quoter simulates swaps internally) but are\n * designed to be called via eth_call for gas-free estimation.\n *\n * Gnosis-chain addresses (hardcoded):\n * BZZ = 0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da\n * WXDAI = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d\n * Quoter = 0xb1e835dc2785b52265711e17fccb0fd018226a6e (SushiSwap V3 QuoterV2)\n * Factory= 0xf78031cbca409f2fb6876bdfdbc1b2df24cf9bef (SushiSwap V3 Factory)\n */\n\n// ─── Interfaces ───────────────────────────────────────────────────────────────\n\ninterface IERC20 {\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\n function transfer(address recipient, uint256 amount) external returns (bool);\n function approve(address spender, uint256 amount) external returns (bool);\n function balanceOf(address account) external view returns (uint256);\n}\n\ninterface IWXDAI {\n function deposit() external payable;\n function withdraw(uint256 amount) external;\n}\n\ninterface ISushiV3Pool {\n function swap(\n address recipient,\n bool zeroForOne,\n int256 amountSpecified,\n uint160 sqrtPriceLimitX96,\n bytes calldata data\n ) external returns (int256 amount0, int256 amount1);\n\n function token0() external view returns (address);\n function token1() external view returns (address);\n}\n\ninterface ISushiV3Factory {\n function getPool(\n address tokenA,\n address tokenB,\n uint24 fee\n ) external view returns (address pool);\n}\n\ninterface IQuoterV2 {\n struct QuoteExactOutputSingleParams {\n address tokenIn;\n address tokenOut;\n uint256 amount;\n uint24 fee;\n uint160 sqrtPriceLimitX96;\n }\n\n function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params)\n external\n returns (\n uint256 amountIn,\n uint160 sqrtPriceX96After,\n uint32 initializedTicksCrossed,\n uint256 gasEstimate\n );\n\n function quoteExactOutput(bytes memory path, uint256 amountOut)\n external\n returns (\n uint256 amountIn,\n uint160[] memory sqrtPriceX96AfterList,\n uint32[] memory initializedTicksCrossedList,\n uint256 gasEstimate\n );\n}\n\ninterface IStampsRegistry {\n function createBatchRegistry(\n address _owner,\n address _nodeAddress,\n uint256 _initialBalancePerChunk,\n uint8 _depth,\n uint8 _bucketDepth,\n bytes32 _nonce,\n bool _immutable\n ) external;\n\n function topUpBatch(bytes32 _batchId, uint256 _topupAmountPerChunk) external;\n}\n\n// ─── Router Contract ──────────────────────────────────────────────────────────\n\ncontract SushiSwapStampsRouter {\n\n // ─── Constants ────────────────────────────────────────────────────────────\n\n /// @notice BZZ token on Gnosis\n address public constant BZZ = 0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da;\n\n /// @notice Wrapped xDAI on Gnosis\n address public constant WXDAI = 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d;\n\n /// @notice SushiSwap V3 QuoterV2 on Gnosis\n address public constant SUSHI_QUOTER = 0xb1E835Dc2785b52265711e17fCCb0fd018226a6e;\n\n /// @notice SushiSwap V3 Factory on Gnosis\n address public constant SUSHI_FACTORY = 0xf78031CBCA409F2FB6876BDFDBc1b2df24cF9bEf;\n\n /// @notice Minimum sqrt price limit (used when selling token0 → token1, zeroForOne=true)\n uint160 internal constant MIN_SQRT_RATIO = 4295128739;\n\n /// @notice Maximum sqrt price limit (used when selling token1 → token0, zeroForOne=false)\n uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;\n\n // Path encoding offsets (bytes): address=20, fee=3, nextOffset=23, popOffset=43\n uint256 private constant ADDR_SIZE = 20;\n uint256 private constant FEE_SIZE = 3;\n uint256 private constant NEXT_OFFSET = 23; // ADDR_SIZE + FEE_SIZE\n uint256 private constant POP_OFFSET = 43; // NEXT_OFFSET + ADDR_SIZE\n\n // ─── Immutables ───────────────────────────────────────────────────────────\n\n IStampsRegistry public immutable stampsRegistry;\n\n // ─── Events ───────────────────────────────────────────────────────────────\n\n event BatchCreatedViaSwap(\n bytes32 indexed batchId,\n address indexed owner,\n address tokenIn,\n uint256 amountIn,\n uint256 bzzAmount\n );\n\n event BatchToppedUpViaSwap(\n bytes32 indexed batchId,\n address tokenIn,\n uint256 amountIn,\n uint256 bzzAmount\n );\n\n // ─── Errors ───────────────────────────────────────────────────────────────\n\n error InvalidCallback();\n error SlippageExceeded(uint256 required, uint256 maximum);\n error InsufficientNativeValue();\n error NativeRefundFailed();\n error BzzTransferFailed();\n error BzzApproveFailed();\n error PoolNotFound();\n error InvalidPath();\n\n // ─── Structs ──────────────────────────────────────────────────────────────\n\n struct CreateBatchParams {\n address owner;\n address nodeAddress;\n uint256 initialBalancePerChunk;\n uint8 depth;\n uint8 bucketDepth;\n bytes32 nonce;\n bool immutable_;\n }\n\n /// @dev Packed into the `data` argument of pool.swap(); threaded through callback chains.\n struct SwapCallbackData {\n bytes path; // remaining path in exactOutput encoding (BZZ-first)\n address payer; // who pays the input token (address(this) for native swaps)\n uint256 maxAmountIn; // slippage ceiling for the final (tokenIn) leg\n }\n\n // ─── Constructor ──────────────────────────────────────────────────────────\n\n constructor(address _stampsRegistry) {\n require(_stampsRegistry != address(0), \"zero registry\");\n stampsRegistry = IStampsRegistry(_stampsRegistry);\n }\n\n receive() external payable {}\n\n // ─── Quote Functions ──────────────────────────────────────────────────────\n // These modify state internally (Quoter simulates swaps) but are designed to\n // be called via eth_call for free gas-less estimation.\n\n /**\n * @notice Quote: how many `tokenIn` are needed to get exactly `bzzAmountOut` BZZ\n * via a single-hop pool.\n * @param tokenIn Input token (use WXDAI for native xDAI quotes)\n * @param fee Pool fee tier (e.g. 500, 3000, 10000)\n * @param bzzAmountOut Exact BZZ amount wanted\n * @return amountIn Input tokens required (before slippage)\n */\n function quoteSingleHop(\n address tokenIn,\n uint24 fee,\n uint256 bzzAmountOut\n ) external returns (uint256 amountIn) {\n (amountIn,,,) = IQuoterV2(SUSHI_QUOTER).quoteExactOutputSingle(\n IQuoterV2.QuoteExactOutputSingleParams({\n tokenIn: tokenIn,\n tokenOut: BZZ,\n amount: bzzAmountOut,\n fee: fee,\n sqrtPriceLimitX96: 0\n })\n );\n }\n\n /**\n * @notice Quote: how many input tokens are needed to get exactly `bzzAmountOut` BZZ\n * via a multi-hop path.\n * @param path Exact-output encoded path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\n * @param bzzAmountOut Exact BZZ amount wanted\n * @return amountIn Input tokens required (before slippage)\n */\n function quoteMultiHop(\n bytes calldata path,\n uint256 bzzAmountOut\n ) external returns (uint256 amountIn) {\n (amountIn,,,) = IQuoterV2(SUSHI_QUOTER).quoteExactOutput(path, bzzAmountOut);\n }\n\n // ─── Create Batch ─────────────────────────────────────────────────────────\n\n /**\n * @notice Swap `tokenIn` → BZZ via the given path and create a Swarm stamp batch.\n * @dev `tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\n * @param path Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\n * @param maxAmountIn Maximum tokenIn to spend (slippage protection)\n * @param bzzAmountOut Exact BZZ needed (= swarmBatchTotal = initialBalancePerChunk × 2^depth)\n * @param p Batch creation parameters\n */\n function createBatch(\n bytes calldata path,\n uint256 maxAmountIn,\n uint256 bzzAmountOut,\n CreateBatchParams calldata p\n ) external {\n address tokenIn = _lastToken(path);\n uint256 balBefore = IERC20(tokenIn).balanceOf(msg.sender);\n _swapExactOutput(path, msg.sender, maxAmountIn, bzzAmountOut);\n uint256 actualAmountIn = balBefore - IERC20(tokenIn).balanceOf(msg.sender);\n bytes32 batchId = _approveBzzAndCreate(p, bzzAmountOut);\n emit BatchCreatedViaSwap(batchId, p.owner, tokenIn, actualAmountIn, bzzAmountOut);\n }\n\n /**\n * @notice Swap native xDAI → BZZ and create a Swarm stamp batch.\n * @dev Send msg.value ≥ maxAmountIn. Excess xDAI is refunded.\n * @param path Exact-output path where the final token MUST be WXDAI:\n * BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\n * @param maxAmountIn Maximum xDAI to spend\n * @param bzzAmountOut Exact BZZ needed\n * @param p Batch creation parameters\n */\n function createBatchNative(\n bytes calldata path,\n uint256 maxAmountIn,\n uint256 bzzAmountOut,\n CreateBatchParams calldata p\n ) external payable {\n if (msg.value < maxAmountIn) revert InsufficientNativeValue();\n IWXDAI(WXDAI).deposit{value: maxAmountIn}();\n _swapExactOutput(path, address(this), maxAmountIn, bzzAmountOut);\n uint256 actualAmountIn = maxAmountIn - IERC20(WXDAI).balanceOf(address(this));\n bytes32 batchId = _approveBzzAndCreate(p, bzzAmountOut);\n emit BatchCreatedViaSwap(batchId, p.owner, address(0), actualAmountIn, bzzAmountOut);\n _refundNative();\n }\n\n // ─── Top Up Batch ─────────────────────────────────────────────────────────\n\n /**\n * @notice Swap `tokenIn` → BZZ and top up an existing Swarm stamp batch.\n * @dev `tokenIn` must be pre-approved to this contract for at least `maxAmountIn`.\n * @param path Exact-output path: BZZ ++ fee ++ [mid ++ fee]* ++ tokenIn\n * @param maxAmountIn Maximum tokenIn to spend\n * @param bzzAmountOut Exact BZZ needed (= topupAmountPerChunk × 2^depth)\n * @param batchId Batch to top up\n * @param topupAmountPerChunk Per-chunk top-up amount (matches registry call)\n */\n function topUp(\n bytes calldata path,\n uint256 maxAmountIn,\n uint256 bzzAmountOut,\n bytes32 batchId,\n uint256 topupAmountPerChunk\n ) external {\n address tokenIn = _lastToken(path);\n uint256 balBefore = IERC20(tokenIn).balanceOf(msg.sender);\n _swapExactOutput(path, msg.sender, maxAmountIn, bzzAmountOut);\n uint256 actualAmountIn = balBefore - IERC20(tokenIn).balanceOf(msg.sender);\n _approveBzzAndTopUp(batchId, topupAmountPerChunk, bzzAmountOut);\n emit BatchToppedUpViaSwap(batchId, tokenIn, actualAmountIn, bzzAmountOut);\n }\n\n /**\n * @notice Swap native xDAI → BZZ and top up an existing Swarm stamp batch.\n * @dev Send msg.value ≥ maxAmountIn. Excess xDAI is refunded.\n * @param path BZZ ++ fee ++ [mid ++ fee]* ++ WXDAI\n * @param maxAmountIn Maximum xDAI to spend\n * @param bzzAmountOut Exact BZZ needed\n * @param batchId Batch to top up\n * @param topupAmountPerChunk Per-chunk top-up amount\n */\n function topUpNative(\n bytes calldata path,\n uint256 maxAmountIn,\n uint256 bzzAmountOut,\n bytes32 batchId,\n uint256 topupAmountPerChunk\n ) external payable {\n if (msg.value < maxAmountIn) revert InsufficientNativeValue();\n IWXDAI(WXDAI).deposit{value: maxAmountIn}();\n _swapExactOutput(path, address(this), maxAmountIn, bzzAmountOut);\n uint256 actualAmountIn = maxAmountIn - IERC20(WXDAI).balanceOf(address(this));\n _approveBzzAndTopUp(batchId, topupAmountPerChunk, bzzAmountOut);\n emit BatchToppedUpViaSwap(batchId, address(0), actualAmountIn, bzzAmountOut);\n _refundNative();\n }\n\n // ─── Uniswap V3 / SushiSwap V3 Swap Callback ─────────────────────────────\n\n /**\n * @notice Called by a SushiSwap V3 pool during swap execution.\n * @dev Implements the Uniswap V3 callback interface (SushiSwap V3 is compatible).\n * For multi-hop swaps, this callback chains into the next pool swap before\n * paying the current pool, routing tokens directly between pools.\n */\n function uniswapV3SwapCallback(\n int256 amount0Delta,\n int256 amount1Delta,\n bytes calldata data\n ) external {\n if (amount0Delta <= 0 && amount1Delta <= 0) revert InvalidCallback();\n\n SwapCallbackData memory cb = abi.decode(data, (SwapCallbackData));\n\n // Decode the first pool in the path to verify the caller is legitimate.\n (address tokenOut, uint24 fee, address tokenIn) = _decodeFirstPool(cb.path);\n address expectedPool = ISushiV3Factory(SUSHI_FACTORY).getPool(tokenOut, tokenIn, fee);\n if (msg.sender != expectedPool) revert InvalidCallback();\n\n // tokenIn is always what we owe: V3 pools sort tokens by address, so\n // tokenIn is token0 iff tokenIn < tokenOut (zeroForOne=true → amount0Delta > 0).\n // Either way the positive delta corresponds to tokenIn.\n address tokenOwed = tokenIn;\n uint256 amountOwed = uint256(amount0Delta > 0 ? amount0Delta : amount1Delta);\n\n if (_hasMultiplePools(cb.path)) {\n // Multi-hop: continue to next pool. Skip the first token from path to get\n // the remaining sub-path: mid ++ fee ++ ... ++ tokenIn\n bytes memory remainingPath = _skipToken(cb.path);\n\n // Decode the next pool info from remaining path.\n (address nextTokenOut, uint24 nextFee, address nextTokenIn) = _decodeFirstPool(remainingPath);\n address nextPool = ISushiV3Factory(SUSHI_FACTORY).getPool(nextTokenOut, nextTokenIn, nextFee);\n if (nextPool == address(0)) revert PoolNotFound();\n\n // Swap in the next pool, sending output directly to msg.sender (current pool)\n // so it receives the tokens it needs without going through this contract.\n bool zeroForOne = nextTokenIn < nextTokenOut;\n ISushiV3Pool(nextPool).swap(\n msg.sender, // recipient = current pool (gets tokenOwed directly)\n zeroForOne,\n -int256(amountOwed), // exact output = amountOwed\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\n abi.encode(SwapCallbackData({\n path: remainingPath,\n payer: cb.payer,\n maxAmountIn: cb.maxAmountIn\n }))\n );\n } else {\n // Final hop: pay tokenOwed from the original payer.\n if (amountOwed > cb.maxAmountIn) {\n revert SlippageExceeded(amountOwed, cb.maxAmountIn);\n }\n\n if (cb.payer == address(this)) {\n // Native xDAI flow: we already hold WXDAI from the deposit.\n if (!IERC20(tokenOwed).transfer(msg.sender, amountOwed)) {\n revert BzzTransferFailed();\n }\n } else {\n // ERC20 flow: pull from user who pre-approved this contract.\n if (!IERC20(tokenOwed).transferFrom(cb.payer, msg.sender, amountOwed)) {\n revert BzzTransferFailed();\n }\n }\n }\n }\n\n // ─── Internal Helpers ─────────────────────────────────────────────────────\n\n /**\n * @dev Execute an exact-output swap for `bzzAmountOut` BZZ using the given path.\n * The path is in exactOutput encoding: BZZ ++ fee ++ [...] ++ tokenIn.\n * BZZ lands in address(this) after the swap completes.\n */\n function _swapExactOutput(\n bytes memory path,\n address payer,\n uint256 maxAmountIn,\n uint256 bzzAmountOut\n ) internal {\n if (path.length < POP_OFFSET) revert InvalidPath();\n\n // Decode the first (and for single-hop, only) pool in the path.\n (address tokenOut, uint24 fee, address tokenIn) = _decodeFirstPool(path);\n if (tokenOut != BZZ) revert InvalidPath();\n\n address pool = ISushiV3Factory(SUSHI_FACTORY).getPool(tokenOut, tokenIn, fee);\n if (pool == address(0)) revert PoolNotFound();\n\n // zeroForOne: true if tokenIn is token0 (address < BZZ)\n bool zeroForOne = tokenIn < tokenOut;\n\n // amountSpecified < 0 → exact output (we want exactly bzzAmountOut of BZZ)\n ISushiV3Pool(pool).swap(\n address(this), // receive BZZ here\n zeroForOne,\n -int256(bzzAmountOut),\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\n abi.encode(SwapCallbackData({\n path: path,\n payer: payer,\n maxAmountIn: maxAmountIn\n }))\n );\n }\n\n /**\n * @dev Approve BZZ to the stamps registry and call createBatchRegistry.\n * Returns the keccak256 batch ID consistent with the registry's derivation.\n */\n function _approveBzzAndCreate(\n CreateBatchParams memory p,\n uint256 bzzAmountOut\n ) internal returns (bytes32 batchId) {\n if (!IERC20(BZZ).approve(address(stampsRegistry), bzzAmountOut)) {\n revert BzzApproveFailed();\n }\n\n stampsRegistry.createBatchRegistry(\n p.owner,\n p.nodeAddress,\n p.initialBalancePerChunk,\n p.depth,\n p.bucketDepth,\n p.nonce,\n p.immutable_\n );\n\n // Registry derives batchId as keccak256(abi.encode(registry, nonce)).\n batchId = keccak256(abi.encode(address(stampsRegistry), p.nonce));\n }\n\n /**\n * @dev Approve BZZ to the stamps registry and call topUpBatch.\n */\n function _approveBzzAndTopUp(\n bytes32 batchId,\n uint256 topupAmountPerChunk,\n uint256 bzzAmountOut\n ) internal {\n if (!IERC20(BZZ).approve(address(stampsRegistry), bzzAmountOut)) {\n revert BzzApproveFailed();\n }\n stampsRegistry.topUpBatch(batchId, topupAmountPerChunk);\n }\n\n /**\n * @dev Unwrap any remaining WXDAI and refund all native xDAI to msg.sender.\n */\n function _refundNative() internal {\n uint256 wxdaiBalance = IERC20(WXDAI).balanceOf(address(this));\n if (wxdaiBalance > 0) {\n IWXDAI(WXDAI).withdraw(wxdaiBalance);\n }\n uint256 nativeBalance = address(this).balance;\n if (nativeBalance > 0) {\n (bool ok,) = msg.sender.call{value: nativeBalance}(\"\");\n if (!ok) revert NativeRefundFailed();\n }\n }\n\n // ─── Path Utilities ───────────────────────────────────────────────────────\n\n /**\n * @dev Returns true if the path encodes more than one pool (length > 43 bytes).\n */\n function _hasMultiplePools(bytes memory path) internal pure returns (bool) {\n return path.length > POP_OFFSET;\n }\n\n /**\n * @dev Decodes the first pool segment from the path:\n * tokenA (20 bytes) ++ fee (3 bytes) ++ tokenB (20 bytes)\n */\n function _decodeFirstPool(bytes memory path)\n internal\n pure\n returns (address tokenA, uint24 fee, address tokenB)\n {\n tokenA = _toAddress(path, 0);\n fee = _toUint24(path, ADDR_SIZE);\n tokenB = _toAddress(path, NEXT_OFFSET);\n }\n\n /**\n * @dev Returns the path with the first token removed (skips ADDR_SIZE + FEE_SIZE bytes).\n * Used to advance through multi-hop paths in the callback.\n */\n function _skipToken(bytes memory path) internal pure returns (bytes memory skipped) {\n uint256 newLen = path.length - NEXT_OFFSET;\n skipped = new bytes(newLen);\n assembly {\n let src := add(add(path, 0x20), NEXT_OFFSET)\n let dst := add(skipped, 0x20)\n // Copy 32 bytes at a time; the last chunk may write up to 31 bytes past\n // newLen, but into the 32-byte-aligned padding that `new bytes` allocates.\n for { let i := 0 } lt(i, newLen) { i := add(i, 32) } {\n mstore(add(dst, i), mload(add(src, i)))\n }\n }\n }\n\n /**\n * @dev Extracts the last 20-byte address from the path (the tokenIn address).\n */\n function _lastToken(bytes memory path) internal pure returns (address token) {\n uint256 offset = path.length - ADDR_SIZE;\n token = _toAddress(path, offset);\n }\n\n /**\n * @dev Reads a 20-byte address from `data` at `offset` using assembly.\n * The address occupies bytes [offset, offset+20) and is right-aligned\n * by shifting the 32-byte word 96 bits right.\n */\n function _toAddress(bytes memory data, uint256 offset) internal pure returns (address addr) {\n assembly {\n addr := shr(96, mload(add(add(data, 0x20), offset)))\n }\n }\n\n /**\n * @dev Reads a 3-byte uint24 from `data` at `offset` using assembly.\n * Shifts the 32-byte word 232 bits right to extract the top 3 bytes.\n */\n function _toUint24(bytes memory data, uint256 offset) internal pure returns (uint24 result) {\n assembly {\n result := shr(232, mload(add(add(data, 0x20), offset)))\n }\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "viaIR": true, + "evmVersion": "paris", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file