// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {LootConstants as C} from "../LootConstants.sol";
import {LootConstantsV4 as C4} from "./LootConstantsV4.sol";
import {MemeTokenV4} from "./MemeTokenV4.sol";
import {RouterErrorsV4} from "./RouterErrorsV4.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
/// The LOOT v4 token surface the router resolves from (#161 D1: `pool()` returns the
/// per-pool ORACLE ADAPTER on v4 rows — the UNION `token.pool()` ethos, transposed).
interface ILootTokenV4 {
function pool() external view returns (address);
}
/// The adapter's provenance getter (vendored OZ V3OracleAdapter): the hook it reads
/// from. `token.pool() → adapter → baseOracleHook()` is the generation-agnostic hook
/// resolution — one router serves EVERY v4 generation's tokens (#165 D9).
interface IOracleAdapterV4 {
function baseOracleHook() external view returns (address);
}
/// The LootHookV4 surface this router binds to (#165 D3 binding inputs, all present
/// on every v4-generation hook).
interface ILootHookV4Router {
function poolKeyFor(address token) external view returns (PoolKey memory);
function poolIdOf(address token) external view returns (PoolId);
function originRoom(PoolId id, address origin) external view returns (uint256);
}
/// @title LootRouterV4 — user-facing swap router for LOOT v4 pools (SPEC §22.5, §24.2; #165)
/// @notice The deployed UNION LootRouter transposed onto the canonical Uniswap V4
/// singleton: keyless, immutable, and fully STORAGE-FREE (#165 D1/D7 — every
/// callback context rides `unlock(data)`; no storage slot is ever written, which
/// also removes the last cross-trade storage contention from the 5/5
/// same-second spray path). External function signatures and the Buy/Sell
/// event shapes are byte-identical to the deployed v3 router (D1/D6), so
/// `tradeTx.ts` sims, the API trade router, and the indexer attribution join
/// transpose with near-zero surface change.
///
/// Structural laws enforced here (#161/#165/#167 router bindings):
/// - Buys TAKE the tokens straight to the BUYER and sells SETTLE straight FROM
/// the SELLER (`seller → PoolManager`, one hop) with the ETH taken DIRECTLY
/// to the seller — the router never holds sale proceeds even transiently,
/// and the ledger's PM-counterparty classification sees the TRADER on every
/// token leg (the #102 routed-seller law, generalized).
/// - SETTLE-BY-DELTA (binding): the router always settles/takes exactly the
/// swap's returned BalanceDelta amounts, never the requested amounts —
/// liquidity-exhaustion partials settle only what executed (F2: the game
/// burn matches actual sold by construction) and CurrencyNotSettled is
/// structurally unreachable from our own math.
/// - Native ETH end to end (#162): no WETH anywhere, `settle{value}` on buys,
/// native `take` to the seller on sells, native refunds.
/// - Fill-to-cap (SPEC §24.2, "a normal user's trade never fails"): buys clamp
/// BEFORE the swap to min(wallet-cap room, token origin room, hook origin
/// room) via probe-then-execute in one unlock — a user on this lane never
/// sees the raw hook-rail revert; boundary semantics stay `<=` (filling
/// exactly to a cap succeeds — v3 parity).
/// - hookData = abi.encode(trader) on every swap (#167 D3 display-attribution
/// channel — tolerated and ignored on-chain, never trusted).
/// - NO harvest/poke code of any kind (#165 D7: the hook's in-swap crank
/// already fires inside every router swap; the gas-stipend requirement is
/// honored by retirement — the web's FIXED gas hints reserve the crank
/// budget up front on every LOOT-frontend trade).
contract LootRouterV4 is IUnlockCallback {
using SafeCast for uint256;
using RouterErrorsV4 for bytes;
// ── errors (all custom, no require-strings — #165 D5) ────────────────────
error DeadlinePassed();
error Slippage();
error NoPool();
error NotPoolManager();
error NotSelf();
/// @notice A price-limited fill executed nothing (spot already past the limit —
/// v4-core's PriceLimitAlreadyExceeded, mapped back to the v3 contract:
/// "zero fill is loud, funds all come back"). Full-refund semantics — no
/// partial state exists when this is thrown.
error NothingFilled();
/// @notice Structurally unreachable: the simulation self-call always reverts.
error ProbeDidNotRevert();
/// @notice The buyer's first-hour origin allowance (cumulative buys per tx.origin,
/// 120% of maxWallet — sells do NOT restore it) is fully spent. Distinct
/// from the wallet cap so integrators can tell the two limits apart; the
/// allowance dies when the pool's LAUNCH_RESTRICTION_WINDOW ends.
error OriginAllowanceSpent();
/// @notice Same-tx simulation and execution disagreed. Fund-safety over anything:
/// any deviation reverts rather than mis-filling (v3 "sim mismatch",
/// promoted to a custom error). Bug-class — never blamed on the user.
error SimMismatch();
/// @notice A native refund/payout send was rejected by the recipient.
error EthTransferFailed();
/// @notice The seller-side token pull returned false (v3 "token pay" require,
/// promoted to a custom error — MemeTokenV4 reverts rather than
/// returning false, so this is belt-and-suspenders on the pinned shape).
error TokenPayFailed();
IPoolManager public immutable poolManager;
// ── unlock actions (context rides unlock(data) — zero storage, #165 D1) ──
uint8 private constant A_BUY = 1;
uint8 private constant A_SELL = 2;
uint8 private constant A_SELL_UPTO = 3;
/// Byte-identical v3 event shapes (#165 D6): identical topic0s keep the indexer
/// attribution join a one-line ROUTER-set membership edit. ACTUAL executed
/// amounts always (ethIn = ethSpent; tokensIn = tokensSold).
event Buy(address indexed token, address indexed buyer, uint256 ethIn, uint256 tokensOut, uint256 ethRefunded);
event Sell(address indexed token, address indexed seller, uint256 tokensIn, uint256 ethOut);
/// @notice The PoolManager argument was not the canonical Uniswap V4 singleton.
error BadPoolManager();
/// @dev K-9 (runbook §1.2) — THE POOLMANAGER BINDING, "the only guard against a
/// mis-passed PoolManager". `C4.POOL_MANAGER` is the canonical Uniswap V4
/// singleton and is byte-identical AT THE SAME ADDRESS on 46630 and 4663
/// (#153), so one constant serves both chains and binding costs nothing.
/// Without it a typo'd or attacker-supplied singleton deploys cleanly, every
/// ceremony readback agrees with itself, and the generation is keyless and
/// uncurable on the wrong PoolManager forever.
constructor(IPoolManager poolManager_) {
if (address(poolManager_) != C4.POOL_MANAGER) revert BadPoolManager();
poolManager = poolManager_;
}
// ==========================================================================
// Resolution — token-addressed, never PoolKey-addressed (#165 D1/D9)
// ==========================================================================
/// @dev `token.pool()` → adapter → `baseOracleHook()` → hook → `poolKeyFor(token)`.
/// Low-level staticcalls keep a clean NoPool revert for ANY non-LOOT-v4 shape
/// (EOA, non-token contract, v3-generation token whose `pool()` is a raw V3
/// pool without the adapter getter — those rows belong to the UNION router).
/// The final `poolIdOf` cross-check pins the derived key to the hook's own
/// registration (set at lockPosition, same launch tx) so a half-launched or
/// foreign-shaped token resolves to NoPool instead of an uninitialized-pool
/// revert deep inside the swap.
///
/// F-01/F-01E (audit 2026-08-13) — THE TOKEN BINDING. Every step above is
/// derived FROM the caller's `token`, so for an attacker-supplied
/// token/adapter/hook trio the `poolIdOf` cross-check is circular: it asks the
/// attacker's own hook whether the attacker's own key is the right one. Without
/// the binding below, `sell(fakeToken, ...)` resolved to a key whose currency1
/// is a DIFFERENT asset and `_sellInner` then pulled THAT asset from the caller
/// (`sell` has no seller parameter — the seller is msg.sender, and every seller
/// grants this router a standing allowance, #165 D2). Pointing the trio at a
/// pool the attacker owns turned it into outright theft. The resolved key must
/// trade the token the caller NAMED — this is the one check that is not derived
/// from attacker-controlled data.
function _resolve(address token)
internal
view
returns (ILootHookV4Router hook, PoolKey memory key, PoolId id)
{
(bool ok, bytes memory data) = token.staticcall(abi.encodeWithSelector(ILootTokenV4.pool.selector));
if (!ok || data.length < 32) revert NoPool();
address adapter = abi.decode(data, (address));
if (adapter == address(0)) revert NoPool();
(ok, data) = adapter.staticcall(abi.encodeWithSelector(IOracleAdapterV4.baseOracleHook.selector));
if (!ok || data.length < 32) revert NoPool();
address h = abi.decode(data, (address));
if (h == address(0)) revert NoPool();
hook = ILootHookV4Router(h);
try hook.poolKeyFor(token) returns (PoolKey memory k) {
key = k;
} catch {
revert NoPool();
}
if (Currency.unwrap(key.currency1) != token) revert NoPool();
id = key.toId();
try hook.poolIdOf(token) returns (PoolId registered) {
if (PoolId.unwrap(registered) != PoolId.unwrap(id)) revert NoPool();
} catch {
revert NoPool();
}
}
/// @notice The most tokens a buy can deliver to msg.sender right now:
/// min(2% wallet-cap room, token-side first-hour origin room, HOOK-side
/// first-hour origin room). Read in the SAME tx the swap runs in — there
/// is no window for another buy to invalidate it (#165 D3.1).
/// @dev Three legs, ported + transposed from the deployed origin-aware router:
/// - wallet leg: v3 `_fillRoom` verbatim (metadataFrozen/capExempt →
/// unbounded; else maxWallet − balanceOf) — the token getters are frozen
/// unchanged by #161 D1.
/// - token origin leg: v3 verbatim (the token still charges `originBought`
/// on every PM → buyer take AND on ERC-6909 claim redemptions, #161 D2).
/// AUDIT-FIX (v4 transposition): #165 D3 words the origin leg as the hook
/// view alone, but a 6909 redeem charges the TOKEN tally without the hook's
/// — dropping this leg would let a clamped fill hit the token's raw
/// OriginFlowCapExceeded on the take. Keeping the v3 leg closes that
/// divergence; it can only ever clamp tighter (fund-safe).
/// - hook rail leg (#165 D3 binding: `hook.originRoom(id, tx.origin)`,
/// token-denominated, max-uint once dead): folded UNCONDITIONALLY — the
/// hook rail charges every hour-1 buy swap at SWAP level, recipient-blind,
/// so even a capExempt buyer is bound by it (unlike v3, where the exempt
/// early-return was correct because the only charge site was the token).
/// @return room max tokens deliverable (type(uint256).max = no bound)
/// @return originBinds true when an origin allowance specifically is the exhausted
/// bound (the wallet itself still has cap room) — drives the
/// OriginAllowanceSpent vs Slippage/NothingFilled choreography.
function _fillRoom(MemeTokenV4 mt, ILootHookV4Router hook, PoolId id)
internal
view
returns (uint256 room, bool originBinds)
{
room = type(uint256).max;
if (!mt.metadataFrozen() && !mt.capExempt(msg.sender)) {
uint256 cap = mt.maxWallet();
uint256 held = mt.balanceOf(msg.sender);
room = held >= cap ? 0 : cap - held;
if (block.timestamp < mt.launchTime() + C.LAUNCH_RESTRICTION_WINDOW) {
uint256 originCap = (cap * C.ORIGIN_BUY_CAP_PCT_OF_MAX) / 100;
uint256 used = mt.originBought(tx.origin);
uint256 tokenOriginRoom = used >= originCap ? 0 : originCap - used;
if (tokenOriginRoom < room) {
room = tokenOriginRoom;
originBinds = tokenOriginRoom == 0;
}
}
}
uint256 hookRoom = hook.originRoom(id, tx.origin);
if (hookRoom < room) {
room = hookRoom;
originBinds = hookRoom == 0;
}
}
// ==========================================================================
// Buys (native ETH in → tokens straight to the buyer)
// ==========================================================================
/// @notice Buy `token` with exact ETH in. Clamps to the wallet cap / origin
/// allowances (fill-to-whichever-binds-first), refunds unused ETH,
/// enforces `minTokensOut` against the ACTUAL post-clamp fill (#165 D8).
/// Signature byte-identical to the deployed v3 router.
function buy(address token, uint256 minTokensOut, uint256 deadline)
external
payable
returns (uint256 tokensOut, uint256 ethRefunded)
{
if (block.timestamp > deadline) revert DeadlinePassed();
// Guard (#165 D3.6): a zero amount is never forwarded (PM SwapAmountCannotBeZero).
if (msg.value == 0) revert Slippage();
(ILootHookV4Router hook, PoolKey memory key, PoolId id) = _resolve(token);
(uint256 room, bool originBinds) = _fillRoom(MemeTokenV4(token), hook, id);
if (room == 0) {
if (originBinds) revert OriginAllowanceSpent();
revert Slippage(); // already at the wallet cap
}
(uint256 ethSpent, uint256 out) = abi.decode(
poolManager.unlock(
abi.encode(A_BUY, msg.sender, key, msg.value, room, uint160(TickMath.MIN_SQRT_PRICE + 1))
),
(uint256, uint256)
);
tokensOut = out;
if (tokensOut < minTokensOut) revert Slippage();
ethRefunded = _refund(msg.value - ethSpent);
emit Buy(token, msg.sender, ethSpent, tokensOut, ethRefunded);
}
/// @notice Buy `token` with UP TO `msg.value` ETH at an average price bounded by
/// `sqrtPriceLimitX96` — the price-limit fill (L3 of the "a normal user's
/// trade never fails" ruling, 2026-07-17). The three clamps (price limit,
/// wallet cap, origin allowances) compose: the fill stops at whichever
/// binds first; unused ETH is refunded. There is NO Slippage() failure
/// mode here — the only reverts are deadline, resolution, and a fill that
/// would execute nothing (NothingFilled — full-refund semantics, with
/// v4-core's limit-past-spot revert mapped back to it on-chain).
/// @param sqrtPriceLimitX96 Worst pool price the fill may reach. v4 orientation is
/// FIXED (native ETH = currency0, #162): buys trade the pool price DOWN, so
/// the limit must be BELOW spot — no `weth < token` derivation exists.
function buyUpTo(address token, uint160 sqrtPriceLimitX96, uint256 deadline)
external
payable
returns (uint256 tokensOut, uint256 ethSpent, uint256 ethRefunded)
{
if (block.timestamp > deadline) revert DeadlinePassed();
if (msg.value == 0) revert NothingFilled();
(ILootHookV4Router hook, PoolKey memory key, PoolId id) = _resolve(token);
(uint256 room, bool originBinds) = _fillRoom(MemeTokenV4(token), hook, id);
if (room == 0) {
if (originBinds) revert OriginAllowanceSpent();
revert NothingFilled();
}
(uint256 spent, uint256 out) = abi.decode(
poolManager.unlock(abi.encode(A_BUY, msg.sender, key, msg.value, room, sqrtPriceLimitX96)),
(uint256, uint256)
);
// A zero fill must be loud, not a silent success — the caller's funds all
// come back with the revert (v3 buyUpTo contract preserved, #165 D3.6).
if (out == 0) revert NothingFilled();
(tokensOut, ethSpent) = (out, spent);
ethRefunded = _refund(msg.value - ethSpent);
// Reuses the Buy event with ACTUAL amounts — the settlement indexer's swap
// lane keeps working unchanged for partial fills (#165 D6).
emit Buy(token, msg.sender, ethSpent, tokensOut, ethRefunded);
}
// ==========================================================================
// Sells (tokens straight FROM the seller → native ETH straight TO the seller)
// ==========================================================================
/// @notice Sell exact `tokensIn` of `token` for ETH; enforces `minEthOut`. Caller
/// must approve this router for `tokensIn` first (direct allowance — #165
/// D2; the router pulls `seller → PoolManager` inside the callback, so the
/// token's transfer hook sees the SELLER on the `→ PM` leg and the ledger
/// credits the sell to the seller, not this router).
/// @dev Settle-by-delta: a liquidity-exhaustion partial (a sell pushing past the
/// locked range) pulls and burns ONLY the executed portion — `minEthOut` is
/// the caller's protection against a dust fill. The event carries executed
/// actuals (#165 D6).
function sell(address token, uint256 tokensIn, uint256 minEthOut, uint256 deadline)
external
returns (uint256 ethOut)
{
if (block.timestamp > deadline) revert DeadlinePassed();
if (tokensIn == 0) revert Slippage();
(, PoolKey memory key,) = _resolve(token);
(uint256 e, uint256 sold) = abi.decode(
poolManager.unlock(
abi.encode(A_SELL, msg.sender, key, tokensIn, uint256(0), uint160(TickMath.MAX_SQRT_PRICE - 1))
),
(uint256, uint256)
);
ethOut = e;
if (ethOut < minEthOut) revert Slippage();
emit Sell(token, msg.sender, sold, ethOut);
}
/// @notice Sell UP TO `tokensIn` of `token` at an average price bounded by
/// `sqrtPriceLimitX96`. Only the portion the limit lets execute ever
/// LEAVES the seller's wallet (F2 law — probe determines `tokensSold`
/// BEFORE anything is pulled, so the game burn matches actual sold);
/// ETH for the sold portion is taken straight to the seller.
/// @param sqrtPriceLimitX96 Worst pool price the fill may reach — sells trade the
/// pool price UP on v4 rows (limit ABOVE spot).
function sellUpTo(address token, uint256 tokensIn, uint160 sqrtPriceLimitX96, uint256 deadline)
external
returns (uint256 ethOut, uint256 tokensSold, uint256 tokensRefunded)
{
if (block.timestamp > deadline) revert DeadlinePassed();
if (tokensIn == 0) revert NothingFilled();
(, PoolKey memory key,) = _resolve(token);
(uint256 e, uint256 sold) = abi.decode(
poolManager.unlock(abi.encode(A_SELL_UPTO, msg.sender, key, tokensIn, uint256(0), sqrtPriceLimitX96)),
(uint256, uint256)
);
(ethOut, tokensSold) = (e, sold);
tokensRefunded = tokensIn - tokensSold; // never pulled — stayed in the wallet
// ACTUAL sold amount in the event — indexer-compatible (#165 D6).
emit Sell(token, msg.sender, tokensSold, ethOut);
}
// ==========================================================================
// The unlock frame
// ==========================================================================
function unlockCallback(bytes calldata data) external returns (bytes memory) {
if (msg.sender != address(poolManager)) revert NotPoolManager();
(uint8 action, address trader, PoolKey memory key, uint256 amount, uint256 room, uint160 limit) =
abi.decode(data, (uint8, address, PoolKey, uint256, uint256, uint160));
// Display-attribution channel (#167 D3): ignored on-chain by hook and indexer
// alike; trader is ALWAYS the entry function's msg.sender — never a parameter.
bytes memory hookData = abi.encode(trader);
if (action == A_BUY) return _buyInner(trader, key, amount, room, limit, hookData);
return _sellInner(trader, key, amount, limit, action == A_SELL_UPTO, hookData);
}
/// @dev Buy body (#165 D3). Unbounded room (graduated/exempt, rail dead) → straight
/// exact-in, no probe — the dominant post-graduation path pays nothing extra.
/// Bounded room → probe-then-execute in ONE unlock: the probe self-call runs
/// the exact-in swap and reverts carrying the BalanceDelta (every PM state
/// change rolls back, transient attestations included); if the quote fits the
/// room, execute exact-in; else execute exact-OUT = room with the same limit —
/// fill to whichever constraint binds first. Same-tx simulation == execution
/// equivalence is asserted (SimMismatch) — no external calls run between
/// probe and execution.
function _buyInner(
address trader,
PoolKey memory key,
uint256 ethIn,
uint256 room,
uint160 limit,
bytes memory hookData
) internal returns (bytes memory) {
BalanceDelta d;
SwapParams memory exactIn =
SwapParams({zeroForOne: true, amountSpecified: -ethIn.toInt256(), sqrtPriceLimitX96: limit});
if (room == type(uint256).max) {
try poolManager.swap(key, exactIn, hookData) returns (BalanceDelta rd) {
d = rd;
} catch (bytes memory reason) {
// v4-core REVERTS on a limit already past spot where v3 returned a
// zero fill — map it back to the loud full-refund contract (#165 D5).
if (reason.leafSelector() == RouterErrorsV4.PRICE_LIMIT_ALREADY_EXCEEDED) revert NothingFilled();
reason.bubble();
}
} else {
(bool gotQuote, int256 q0, int256 q1) = _probeBuy(key, exactIn, hookData);
// A ZERO quote is a genuine zero fill inside the caller's limit (the
// launch frame parks spot ABOVE the locked range top — a limit stopping
// in that no-liquidity gap executes nothing): loud full-refund, never
// the exact-out fallback (which would demand `room` tokens that the
// limit cannot deliver).
if (gotQuote && q1 <= 0) revert NothingFilled();
if (gotQuote && uint256(q1) <= room) {
d = poolManager.swap(key, exactIn, hookData);
if (d.amount0() != q0 || d.amount1() != q1) revert SimMismatch();
} else {
// Quote over the room (or the hour-1 rail refused the full quote):
// exact-out = room fills exactly to the binding constraint. Boundary
// stays `<=` — filling exactly to cap/allowance succeeds (v3 parity).
d = poolManager.swap(
key,
SwapParams({zeroForOne: true, amountSpecified: room.toInt256(), sqrtPriceLimitX96: limit}),
hookData
);
// The probe proved > room tokens are available inside this limit, so
// a genuine exact-out must deliver the full room — anything else is
// the same bug-class the exact-in assert guards.
if (d.amount1() < 0 || uint256(uint128(d.amount1())) != room) revert SimMismatch();
}
}
uint256 spent = d.amount0() < 0 ? uint256(uint128(-d.amount0())) : 0;
uint256 out = d.amount1() > 0 ? uint256(uint128(d.amount1())) : 0;
// Settle-by-delta: exactly what the swap says, never the request.
if (spent > 0) poolManager.settle{value: spent}();
if (out > 0) poolManager.take(key.currency1, trader, out); // straight to the buyer
return abi.encode(spent, out);
}
/// @dev The probe leg of probe-then-execute. Outcomes:
/// - delta payload (64 bytes) → the exact-in quote;
/// - hour-1 rail refusal (OriginFlowCapExceeded leaf, ERC-7751-wrapped by
/// v4-core) → `gotQuote = false`: the full quote exceeds the origin
/// allowance, caller clamps to exact-out = room (room <= hook room by
/// construction, so the clamped execution passes the rail);
/// - limit already past spot → NothingFilled (full refund, v3 contract);
/// - anything else bubbles unchanged.
function _probeBuy(PoolKey memory key, SwapParams memory params, bytes memory hookData)
internal
returns (bool gotQuote, int256 q0, int256 q1)
{
try this.probeSwap(key, params, hookData) {
revert ProbeDidNotRevert(); // unreachable: the probe always reverts
} catch (bytes memory reason) {
if (reason.length == 64) {
(q0, q1) = abi.decode(reason, (int256, int256));
gotQuote = true;
} else {
bytes4 leaf = reason.leafSelector();
if (leaf == RouterErrorsV4.PRICE_LIMIT_ALREADY_EXCEEDED) revert NothingFilled();
if (leaf != RouterErrorsV4.ORIGIN_FLOW_CAP_EXCEEDED) reason.bubble();
}
}
}
/// @dev Sell body. `probeFirst` (sellUpTo) determines the executed portion BEFORE
/// anything leaves the wallet; plain sell needs no probe — exact-in under
/// settle-by-delta handles liquidity-exhaustion partials, `minEthOut` (checked
/// at the entry) protects the seller. The ONLY `→ PM` token leg is
/// `seller → PM` and the ETH is taken DIRECTLY to the seller (#165 D1).
function _sellInner(
address seller,
PoolKey memory key,
uint256 tokensIn,
uint160 limit,
bool probeFirst,
bytes memory hookData
) internal returns (bytes memory) {
uint256 toSell = tokensIn;
int256 q0;
int256 q1;
if (probeFirst) {
try this.probeSwap(
key,
SwapParams({zeroForOne: false, amountSpecified: -tokensIn.toInt256(), sqrtPriceLimitX96: limit}),
hookData
) {
revert ProbeDidNotRevert();
} catch (bytes memory reason) {
if (reason.length != 64) {
// Spot already at/past the limit → loud zero-fill, nothing pulled.
if (reason.leafSelector() == RouterErrorsV4.PRICE_LIMIT_ALREADY_EXCEEDED) revert NothingFilled();
reason.bubble();
}
(q0, q1) = abi.decode(reason, (int256, int256));
}
// Seller-perspective deltas: amount1 < 0 = tokens in, amount0 > 0 = ETH out.
if (q1 >= 0 || q0 <= 0) revert NothingFilled();
toSell = uint256(-q1);
}
BalanceDelta d = poolManager.swap(
key,
SwapParams({zeroForOne: false, amountSpecified: -toSell.toInt256(), sqrtPriceLimitX96: limit}),
hookData
);
// Simulation == execution equivalence (identical tick path, no external calls
// between probe and execution) — asserted anyway: any deviation reverts rather
// than mis-accounting a burn (v3 "sim mismatch", fund-safe).
if (probeFirst && (d.amount0() != q0 || d.amount1() != q1)) revert SimMismatch();
uint256 sold = d.amount1() < 0 ? uint256(uint128(-d.amount1())) : 0;
uint256 ethOut = d.amount0() > 0 ? uint256(uint128(d.amount0())) : 0;
if (sold > 0) {
poolManager.sync(key.currency1);
// Settle FROM the seller: `seller → PM`, one hop — the same allowance the
// seller granted this router; the ledger sees the seller on the token leg.
if (!MemeTokenV4(Currency.unwrap(key.currency1)).transferFrom(seller, address(poolManager), sold)) {
revert TokenPayFailed();
}
poolManager.settle();
}
if (ethOut > 0) poolManager.take(key.currency0, seller, ethOut); // straight to the seller
return abi.encode(ethOut, sold);
}
/// @notice Same-tx swap simulation (the canonical V4Quoter revert-capture pattern,
/// v3 `_probeSwap` transposed): run the swap for real, then revert carrying
/// the (amount0, amount1) deltas — the entire probe frame (pool state, hook
/// rail charges, transient attestations) rolls back. A 64-byte payload is
/// unambiguous against genuine reverts: bare custom errors are 4 bytes,
/// one-arg 36, two-arg 68, panics 36, Error(string) >= 100, WrappedError
/// >= 228 — nothing canonical is exactly 64.
/// @dev Self-callable only; PM is already unlocked inside our own unlock frame
/// (a direct external call meets ManagerLocked at the PM and can move nothing).
/// NOT gas-capped — DEVIATION from the #165 D3 parenthetical, recorded: the
/// probe result is load-bearing (a capped probe that OOGs would misread as a
/// foreign revert), EIP-150's 63/64 already bounds the child frame, and every
/// callee is pinned platform code (v3 `_probeSwap` parity, which was uncapped).
function probeSwap(PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) external {
if (msg.sender != address(this)) revert NotSelf();
BalanceDelta d = poolManager.swap(key, params, hookData);
bytes memory payload = abi.encode(int256(d.amount0()), int256(d.amount1()));
assembly ("memory-safe") {
revert(add(payload, 32), mload(payload))
}
}
// ==========================================================================
// internals
// ==========================================================================
/// @dev Native refund of the unspent ETH (exact-output / price-limit fills leave
/// change). Computed from msg.value, never from balance — stray dust can
/// never ride a refund.
function _refund(uint256 leftover) internal returns (uint256 refunded) {
if (leftover > 0) {
(bool ok,) = msg.sender.call{value: leftover}("");
if (!ok) revert EthTransferFailed();
refunded = leftover;
}
}
// NO receive()/fallback: the router never takes currency to itself (buys take to
// the buyer, sells take to the seller) and refunds spend its own msg.value — any
// unsolicited native send reverts. NO storage exists anywhere in this contract.
}[
{
"type": "constructor",
"inputs": [
{
"name": "poolManager_",
"type": "address",
"internalType": "contract IPoolManager"
}
],
"stateMutability": "nonpayable"
},
{
"name": "BadPoolManager",
"type": "error",
"inputs": []
},
{
"name": "DeadlinePassed",
"type": "error",
"inputs": []
},
{
"name": "EthTransferFailed",
"type": "error",
"inputs": []
},
{
"name": "NoPool",
"type": "error",
"inputs": []
},
{
"name": "NotPoolManager",
"type": "error",
"inputs": []
},
{
"name": "NotSelf",
"type": "error",
"inputs": []
},
{
"name": "NothingFilled",
"type": "error",
"inputs": []
},
{
"name": "OriginAllowanceSpent",
"type": "error",
"inputs": []
},
{
"name": "ProbeDidNotRevert",
"type": "error",
"inputs": []
},
{
"name": "SimMismatch",
"type": "error",
"inputs": []
},
{
"name": "Slippage",
"type": "error",
"inputs": []
},
{
"name": "TokenPayFailed",
"type": "error",
"inputs": []
},
{
"name": "Buy",
"type": "event",
"inputs": [
{
"name": "token",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "buyer",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "ethIn",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "tokensOut",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "ethRefunded",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "Sell",
"type": "event",
"inputs": [
{
"name": "token",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "seller",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "tokensIn",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
},
{
"name": "ethOut",
"type": "uint256",
"indexed": false,
"internalType": "uint256"
}
],
"anonymous": false
},
{
"name": "buy",
"type": "function",
"inputs": [
{
"name": "token",
"type": "address",
"internalType": "address"
},
{
"name": "minTokensOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "tokensOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "ethRefunded",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "payable"
},
{
"name": "buyUpTo",
"type": "function",
"inputs": [
{
"name": "token",
"type": "address",
"internalType": "address"
},
{
"name": "sqrtPriceLimitX96",
"type": "uint160",
"internalType": "uint160"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "tokensOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "ethSpent",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "ethRefunded",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "payable"
},
{
"name": "poolManager",
"type": "function",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract IPoolManager"
}
],
"stateMutability": "view"
},
{
"name": "probeSwap",
"type": "function",
"inputs": [
{
"name": "key",
"type": "tuple",
"components": [
{
"name": "currency0",
"type": "address",
"internalType": "Currency"
},
{
"name": "currency1",
"type": "address",
"internalType": "Currency"
},
{
"name": "fee",
"type": "uint24",
"internalType": "uint24"
},
{
"name": "tickSpacing",
"type": "int24",
"internalType": "int24"
},
{
"name": "hooks",
"type": "address",
"internalType": "contract IHooks"
}
],
"internalType": "struct PoolKey"
},
{
"name": "params",
"type": "tuple",
"components": [
{
"name": "zeroForOne",
"type": "bool",
"internalType": "bool"
},
{
"name": "amountSpecified",
"type": "int256",
"internalType": "int256"
},
{
"name": "sqrtPriceLimitX96",
"type": "uint160",
"internalType": "uint160"
}
],
"internalType": "struct SwapParams"
},
{
"name": "hookData",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"name": "sell",
"type": "function",
"inputs": [
{
"name": "token",
"type": "address",
"internalType": "address"
},
{
"name": "tokensIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minEthOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "ethOut",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "sellUpTo",
"type": "function",
"inputs": [
{
"name": "token",
"type": "address",
"internalType": "address"
},
{
"name": "tokensIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "sqrtPriceLimitX96",
"type": "uint160",
"internalType": "uint160"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "ethOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "tokensSold",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "tokensRefunded",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"name": "unlockCallback",
"type": "function",
"inputs": [
{
"name": "data",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [
{
"name": "",
"type": "bytes",
"internalType": "bytes"
}
],
"stateMutability": "nonpayable"
}
]0x60806040526004361015610011575f80fd5b5f3560e01c80630b8bee45146108435780636f0d41e51461066b57806391dd73461461061a57806392cdbac5146104a8578063a59ac6dd146102ae578063dc4c90d31461026b5763f74dbdb914610066575f80fd5b346102675760803660031901126102675761007f6109fc565b602435604435916001600160a01b038316809303610267576064354211610258578115610249575f61015d936100b4836116f9565b50919050610113604051926003602085015233604085015260608401906001600160a01b036080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b6101008201859052610120820183905261014080830191909152815261013b61016082610ada565b604051809581926348c8949160e01b8352602060048401526024830190610a6a565b0381836001600160a01b037f0000000000000000000000008366a39cc670b4001a1121b8f6a443a643e40951165af190811561023e576101b5610218926101be955f9161021c575b5060208082518301019101610b7b565b80959194610b91565b906040518581528460208201527fa082022e93cfcd9f1da5f9236718053910f7e840da080c789c7845698dc032ff60406001600160a01b0333941692a3604051938493846040919493926060820195825260208201520152565b0390f35b61023891503d805f833e6102308183610ada565b810190610b18565b5f6101a5565b6040513d5f823e3d90fd5b631a41214560e11b5f5260045ffd5b63387b2e5560e11b5f5260045ffd5b5f80fd5b34610267575f3660031901126102675760206040516001600160a01b037f0000000000000000000000008366a39cc670b4001a1121b8f6a443a643e40951168152f35b6060366003190112610267576102c26109fc565b604435421161025857341561045d57806102f26102e66001600160a01b03936116f9565b91949093169384611991565b811561048657506103a7915f9161035a604051926001602085015233604085015260608401906001600160a01b036080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b346101008301526101208201526401000276a461014080830191909152815261038561016082610ada565b604051809381926348c8949160e01b8352602060048401526024830190610a6a565b0381836001600160a01b037f0000000000000000000000008366a39cc670b4001a1121b8f6a443a643e40951165af1801561023e576103f6915f9161046c575060208082518301019101610b7b565b9091602435821061045d578261041661041160409534610b91565b611cbc565b845191825260208201849052604082018190529133917ef93dbdb72854b6b6fb35433086556f2635fc83c37080c667496fecfa650fb490606090a382519182526020820152f35b6307dd37f760e41b5f5260045ffd5b61048091503d805f833e6102308183610ada565b836101a5565b610499576307dd37f760e41b5f5260045ffd5b63465fc0e360e11b5f5260045ffd5b34610267576080366003190112610267576104c16109fc565b602435606435421161025857801561045d575f61057b916104e1846116f9565b50919050610540604051926002602085015233604085015260608401906001600160a01b036080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b610100820152610120810182905273fffd8963efd1fc6a506488495d951d5263988d2561014080830191909152815261038561016082610ada565b0381836001600160a01b037f0000000000000000000000008366a39cc670b4001a1121b8f6a443a643e40951165af1801561023e576105ca915f9161046c575060208082518301019101610b7b565b91604435821061045d5760209260405190815282848201527fa082022e93cfcd9f1da5f9236718053910f7e840da080c789c7845698dc032ff60406001600160a01b0333941692a3604051908152f35b346102675760203660031901126102675760043567ffffffffffffffff811161026757610657610651610218923690600401610a3c565b90610bc1565b604051918291602083526020830190610a6a565b3461026757366003190161012081126102675760a0136102675760603660a3190112610267576101043567ffffffffffffffff8111610267576106b2903690600401610a3c565b9030330361083457604051633cf3645360e21b8152916001600160a01b036106d86109fc565b1660048401526001600160a01b036106ee610a12565b16602484015260443562ffffff81168091036102675760448401526064358060020b8091036102675760648401526084356001600160a01b03811680910361026757608484015260a4358015158091036102675760a484015260c43560c484015260e4356001600160a01b03811680910361026757838281936020956101449460e485015261012061010485015281610124850152848401375f838284010152601f801991011681010301815f6001600160a01b037f0000000000000000000000008366a39cc670b4001a1121b8f6a443a643e40951165af190811561023e575f91610802575b5060405160208101918060801d600f0b8352600f0b6040820152604081526107fe606082610ada565b5190fd5b90506020813d60201161082c575b8161081d60209383610ada565b810103126102675751816107d5565b3d9150610810565b6314e1dbf760e11b5f5260045ffd5b6060366003190112610267576108576109fc565b61085f610a12565b906044354211610258573415610249576001600160a01b0391610890610884836116f9565b91959094169485611991565b81156109e95750916001600160a01b035f9261092794610901604051946001602087015233604087015260608601906001600160a01b036080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b346101008501526101208401521661014080830191909152815261038561016082610ada565b0381836001600160a01b037f0000000000000000000000008366a39cc670b4001a1121b8f6a443a643e40951165af1801561023e57610976915f9161046c575060208082518301019101610b7b565b90918115610249576102189061098f6104118534610b91565b6040805186815260208101869052908101829052909133917ef93dbdb72854b6b6fb35433086556f2635fc83c37080c667496fecfa650fb490606090a3604051938493846040919493926060820195825260208201520152565b61049957631a41214560e11b5f5260045ffd5b600435906001600160a01b038216820361026757565b602435906001600160a01b038216820361026757565b35906001600160a01b038216820361026757565b9181601f840112156102675782359167ffffffffffffffff8311610267576020838186019501011161026757565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b60a0810190811067ffffffffffffffff821117610aaa57604052565b634e487b7160e01b5f52604160045260245ffd5b6060810190811067ffffffffffffffff821117610aaa57604052565b90601f8019910116810190811067ffffffffffffffff821117610aaa57604052565b67ffffffffffffffff8111610aaa57601f01601f191660200190565b6020818303126102675780519067ffffffffffffffff8211610267570181601f8201121561026757805190610b4c82610afc565b92610b5a6040519485610ada565b8284526020838301011161026757815f9260208093018386015e8301015290565b9190826040910312610267576020825192015190565b91908203918211610b9e57565b634e487b7160e01b5f52601160045260245ffd5b90816020910312610267575190565b91906060906001600160a01b037f0000000000000000000000008366a39cc670b4001a1121b8f6a443a643e409511680330361168857845f92810103610140811261026757853560ff8116809103610267576020870135916001600160a01b0383168093036102675760a090603f1901126102675760405190610c4382610a8e565b610c4f60408901610a28565b8252610c5c868901610a28565b9560208301968752608089013562ffffff8116810361026757604084015260a08901358060020b81036102675781840152610c9960c08a01610a28565b608084015260e089013591610cb76101206101008c01359b01610a28565b916040519a8660208d015260208c52610cd160408d610ada565b6001831461119d5750506003149186918291808561105b575b5088811261104c579a6020916001600160a01b03610d0c610d439c9d9e611cf2565b9160405192610d1a84610abe565b8c8452858401521660408201526040519a8b928392633cf3645360e21b84528960048501611d02565b03818a8a5af197881561104157879861100d575b5082610fe5575b5050610fd65784600f0b8481125f14610fce57610d8b6fffffffffffffffffffffffffffffffff91611d84565b16945b60801d8481600f0b135f14610fc6576fffffffffffffffffffffffffffffffff16955b85610e65575b5085610de2575b50505050604051916020830152604082015260408152610ddf606082610ada565b90565b516001600160a01b0316823b15610e6157604051630b0d9c0960e01b81526001600160a01b03918216600482015291166024820152604481018590529082908290606490829084905af18015610e5657610e3e575b8080610dbe565b610e49828092610ada565b610e535780610e37565b80fd5b6040513d84823e3d90fd5b8380fd5b6001600160a01b03815116843b15610fc25760405190632961046560e21b82526004820152858160248183895af18015610fb757908691610f9e575b50906001600160a01b0360209151166064604051809481936323b872dd60e01b83528860048401528960248401528b60448401525af1908115610f3c578591610f6f575b5015610f4757604051630476982d60e21b815260208160048188885af18015610f3c5715610db757610f2e9060203d602011610f35575b610f268183610ada565b810190610bb2565b505f610db7565b503d610f1c565b6040513d87823e3d90fd5b7fffd68ba4000000000000000000000000000000000000000000000000000000008452600484fd5b610f91915060203d602011610f97575b610f898183610ada565b810190611979565b5f610ee5565b503d610f7f565b81610fa891610ada565b610fb357845f610ea1565b8480fd5b6040513d88823e3d90fd5b8580fd5b508395610db1565b508394610d8e565b636c787da960e11b8452600484fd5b608088901d600f0b1480159250610fff575b505f80610d5e565b905085600f0b14155f610ff7565b9097506020813d602011611039575b8161102960209383610ada565b810103126102675751965f610d57565b3d915061101c565b6040513d89823e3d90fd5b6393dafdf160e01b8952600489fd5b925050915086811261118e5761107090611cf2565b6040519061107d82610abe565b87825260208201526001600160a01b0382166040820152303b1561118a5786808b6110be936040519485928392636f0d41e560e01b84528a60048501611d02565b038183305af19182611175575b5050611166576110d9611697565b604081510361112d57806020806110f593518301019101610b7b565b91878312801590611123575b6111145761110e83611cf2565b5f610cea565b631a41214560e11b8852600488fd5b5087821315611101565b86637c9c6e8f60e01b6001600160e01b031961114884611da4565b16146111575750602081519101fd5b631a41214560e11b8152600490fd5b631057d58560e11b8652600486fd5b8161117f91610ada565b61118a57865f6110cb565b8680fd5b6393dafdf160e01b8752600487fd5b915096999391925f98969598505f8112611679576111c26001600160a01b0391611cf2565b93604051946111d086610abe565b6001865260208601521660408401819052905f1981036113c35750509060209161120e6040519485938493633cf3645360e21b855260048501611d02565b03818a865af187918161138f575b50611245578661122a611697565b90637c9c6e8f60e01b6001600160e01b031961114884611da4565b959192939495945b8560801d8381600f0b125f14611387576112776fffffffffffffffffffffffffffffffff91611d84565b16955b8381600f0b135f1461137f576fffffffffffffffffffffffffffffffff16935b86611338575b846112c7575b5050505090610ddf9160405193602085015260408401526040835282610ada565b516001600160a01b0316823b15610e6157604051630b0d9c0960e01b81526001600160a01b03918216600482015291166024820152604481018490529082908290606490829084905af18015610e5657611323575b80806112a6565b61132e828092610ada565b610e53578061131c565b604051630476982d60e21b81526020816004818b885af18015610f3c57611360575b506112a0565b6113789060203d602011610f3557610f268183610ada565b505f61135a565b50829361129a565b50829561127a565b9091506020813d6020116113bb575b816113ab60209383610ada565b810103126102675751905f61121c565b3d915061139e565b915f9a969798949391999a505f5f5f303b1561026757604051636f0d41e560e01b81525f81806113f88b8a8a60048501611d02565b038183305af19081611664575b5061165557611412611697565b9260408451145f14611605575050508060208061143493518301019101610b7b565b93909a60015b80806115fb575b6115ec57806115e2575b156114f4575050906020916114746040519586938493633cf3645360e21b855260048501611d02565b038188885af1918215610f3c5785926114c0575b5081978260801d600f0b14918215926114b2575b50501561124d57636c787da960e11b8352600483fd5b600f0b141590505f8061149c565b9091506020813d6020116114ec575b816114dc60209383610ada565b810103126102675751905f611488565b3d91506114cf565b9350809a5087919250126115d357906020916040519161151383610abe565b600183528a84840152604083015261153f6040519485938493633cf3645360e21b855260048501611d02565b038187875af19081156115c8578491611596575b5080968482600f0b129182156115795750501561124d57636c787da960e11b8352600483fd5b6fffffffffffffffffffffffffffffffff16141590505f8061149c565b90506020813d6020116115c0575b816115b160209383610ada565b8101031261026757515f611553565b3d91506115a4565b6040513d86823e3d90fd5b6393dafdf160e01b8652600486fd5b508185111561144b565b631a41214560e11b8a5260048afd5b5089861315611441565b90959263ffffffff60e09e939e1b61161c82611da4565b16637c9c6e8f60e01b8114611646576318f325b760e01b0361163e575061143a565b602081519101fd5b631a41214560e11b8c5260048cfd5b631057d58560e11b8a5260048afd5b611671919b505f90610ada565b5f995f611405565b6393dafdf160e01b5f5260045ffd5b63570c108560e11b5f5260045ffd5b3d156116c1573d906116a882610afc565b916116b66040519384610ada565b82523d5f602084013e565b606090565b9081602091031261026757516001600160a01b03811681036102675790565b51906001600160a01b038216820361026757565b60405161170581610a8e565b5f81525f60208201525f60408201525f60608201525f6080820152505f8060405160208101906316f0115b60e01b825260048152611744602482610ada565b5190845afa611751611697565b9015801561196e575b61187f57611779816020806001600160a01b03945183010191016116c6565b16801561187f575f80916040516020810190630677875760e01b8252600481526117a4602482610ada565b51915afa6117b0611697565b90158015611963575b61187f576117d8816020806001600160a01b03945183010191016116c6565b1690811561187f5781926001600160a01b0360405192630438133960e31b8452169182600482015260a081602481875afa5f91816118c2575b506118255763f591b27760e01b5f5260045ffd5b92826001600160a01b036020860151160361187f57602060a0852093602460405180948193630c01001560e21b835260048301525afa5f918161188e575b506118775763f591b27760e01b5f5260045ffd5b820361187f57565b63f591b27760e01b5f5260045ffd5b9091506020813d6020116118ba575b816118aa60209383610ada565b810103126102675751905f611863565b3d915061189d565b90915060a0813d60a01161195b575b816118de60a09383610ada565b8101031261026757604051906118f382610a8e565b6118fc816116e5565b825261190a602082016116e5565b6020830152604081015162ffffff811681036102675760408301526060810151908160020b820361026757608091606084015201516001600160a01b0381168103610267576080820152905f611811565b3d91506118d1565b5060208151106117b9565b50602081511061175a565b90816020910312610267575180151581036102675790565b92905f926001600160a01b035f199516604051637d9e636160e11b8152602081600481855afa90811561023e575f91611c9d575b501580611c4c575b611a54575b506001600160a01b039160446020926040519485938492633b4e022560e21b84526004840152326024840152165afa90811561023e575f91611a22575b50838110611a1a5750565b925082159150565b90506020813d602011611a4c575b81611a3d60209383610ada565b8101031261026757515f611a0f565b3d9150611a30565b60405163f8b45b0560e01b81529550602086600481845afa95861561023e575f96611c18575b506040516370a0823160e01b8152336004820152602081602481855afa90811561023e575f91611be6575b50868110611bd657505f905b819660405163790ca41360e01b8152602081600481865afa90811561023e575f91611ba4575b50610e108101809111610b9e574210611af2575b50506119d2565b607881029080820460781490151715610b9e5760206064602492049260405192838092634ede6b6160e11b82523260048301525afa90811561023e575f91611b72575b50818110611b635750505f905b8110611b50575b8080611aeb565b9450841593506001600160a01b03611b49565b611b6c91610b91565b90611b42565b90506020813d602011611b9c575b81611b8d60209383610ada565b8101031261026757515f611b35565b3d9150611b80565b90506020813d602011611bce575b81611bbf60209383610ada565b8101031261026757515f611ad7565b3d9150611bb2565b611be09087610b91565b90611ab1565b90506020813d602011611c10575b81611c0160209383610ada565b8101031261026757515f611aa5565b3d9150611bf4565b9095506020813d602011611c44575b81611c3460209383610ada565b810103126102675751945f611a7a565b3d9150611c27565b5060405163f295344f60e01b8152336004820152602081602481855afa90811561023e575f91611c7e575b50156119cd565b611c97915060203d602011610f9757610f898183610ada565b5f611c77565b611cb6915060203d602011610f9757610f898183610ada565b5f6119c5565b905f9180611cc75750565b9091505f80808084335af1611cda611697565b5015611ce35790565b630db2c7f160e31b5f5260045ffd5b600160ff1b8114610b9e575f0390565b6001600160a01b036040610ddf9594611d5b84610120966001600160a01b036080809282815116855282602082015116602086015262ffffff6040820151166040860152606081015160020b6060860152015116910152565b8051151560a0850152602081015160c085015201511660e0820152816101008201520190610a6a565b600f0b6f7fffffffffffffffffffffffffffffff198114610b9e575f0390565b5f5b60038110611dc457508051600411611dbf576020015190565b505f90565b906004815110611e1257602081015190636f40479b60e01b6001600160e01b0319831601611e0c57611df590611e18565b919015611e06575090600101611da6565b91505090565b50905090565b50505f90565b80516003198101906060908211610b9e5760808210611ea4576064830151928284118015611eb7575b611ead5783611e57910193602485015193610b91565b601f198101908111610b9e578211611ea45750611e7381610afc565b611e806040519182610ada565b8181526044611e8e83610afc565b602083019290601f19013684379301905e600191565b929150505f9190565b50929150505f9190565b506020611ec48585610b91565b10611e4156fea264697066735822122064094498f477432bb347ae45a853bc6c7fbbda401edb3a20a67eabda67b797f064736f6c63430008240033
| Token | Symbol | Balance | Price | Value |
|---|---|---|---|---|
| no token holdings | ||||
| Type | Age | Block | Details |
|---|---|---|---|
| no cross-chain L1↔L2 transactions for this address | |||
| Transaction Hash | Method | Block | Age | From | To | Type | Item | ||
|---|---|---|---|---|---|---|---|---|---|
| no NFT transfers for this address yet | |||||||||
| Transaction Hash | Method | Block | Age | From | To | Amount | Token | ||
|---|---|---|---|---|---|---|---|---|---|
| no token transfers for this address yet | |||||||||
| Txn Hash | Age | Event | Topics / Data |
|---|---|---|---|
| 0x388b3d…d3fd9a | 3 hrs agoMon, 17 Aug 2026 22:39:32 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…56a71007 [1] 0x000000000000…4d425afc data: 0x000000000000000000…7528df9c |
| 0x2294bc…6163b6 | 3 hrs agoMon, 17 Aug 2026 22:33:28 UTC | 0xa08202…32ff | [0] 0x000000000000…30e71007 [1] 0x000000000000…e7157a5c data: 0x000000000000000000…5a378afc |
| 0xf47e30…fb83fd | 3 hrs agoMon, 17 Aug 2026 22:33:16 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…30e71007 [1] 0x000000000000…e7157a5c data: 0x000000000000000000…00000000 |
| 0x8ff0ec…299962 | 3 hrs agoMon, 17 Aug 2026 22:31:47 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…505a1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x19efc2…d4f5f4 | 3 hrs agoMon, 17 Aug 2026 22:30:49 UTC | 0xa08202…32ff | [0] 0x000000000000…6c4d1007 [1] 0x000000000000…e7157a5c data: 0x000000000000000000…4ef7580c |
| 0x163620…3bd83d | 3 hrs agoMon, 17 Aug 2026 22:30:44 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…6c4d1007 [1] 0x000000000000…e7157a5c data: 0x000000000000000000…00000000 |
| 0x61d38b…be227a | 4 hrs agoMon, 17 Aug 2026 21:58:36 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…30e71007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x898f0a…00f35a | 4 hrs agoMon, 17 Aug 2026 21:58:17 UTC | 0xa08202…32ff | [0] 0x000000000000…6c4d1007 [1] 0x000000000000…e7157a5c data: 0x000000000000000000…1ae9ce53 |
| 0xdbfdeb…622806 | 4 hrs agoMon, 17 Aug 2026 21:57:57 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…6c4d1007 [1] 0x000000000000…e7157a5c data: 0x000000000000000000…00000000 |
| 0xff1aff…0823b8 | 6 hrs agoMon, 17 Aug 2026 19:53:54 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…6c4d1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0xf66fd4…9ded30 | 6 hrs agoMon, 17 Aug 2026 19:42:25 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…142c1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0xa0dfed…ce4054 | 6 hrs agoMon, 17 Aug 2026 19:41:30 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…6a501007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0xda5488…811eab | 6 hrs agoMon, 17 Aug 2026 19:39:15 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…982d1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x65e15c…d0944a | 6 hrs agoMon, 17 Aug 2026 19:35:59 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…01611007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x788c40…489c9c | 6 hrs agoMon, 17 Aug 2026 19:34:32 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…49081007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x5c3acb…b5bee9 | 6 hrs agoMon, 17 Aug 2026 19:33:21 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…b12d1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x37ab0f…f9b34a | 6 hrs agoMon, 17 Aug 2026 19:19:57 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…bc361007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0xd5015e…ca0cb5 | 6 hrs agoMon, 17 Aug 2026 19:18:59 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…4ea81007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0xcbb698…4a7588 | 6 hrs agoMon, 17 Aug 2026 19:18:51 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…e6fa1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x72256a…d9725c | 6 hrs agoMon, 17 Aug 2026 19:13:42 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…09871007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x1680af…f19d2a | 6 hrs agoMon, 17 Aug 2026 19:13:34 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…09971007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x83aa62…f7f3ec | 6 hrs agoMon, 17 Aug 2026 19:11:57 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…a35f1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x7a1395…011d37 | 6 hrs agoMon, 17 Aug 2026 19:10:44 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…3a531007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x8e959c…1158f9 | 7 hrs agoMon, 17 Aug 2026 18:58:12 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…90de1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| 0x84c31f…0f2ab8 | 7 hrs agoMon, 17 Aug 2026 18:56:40 UTC | 0x00f93d…0fb4 | [0] 0x000000000000…078d1007 [1] 0x000000000000…a53f4b6a data: 0x000000000000000000…00000000 |
| Transaction Hash | Method ? | Block | Age | From | To | Amount | Txn Fee ? | ||
|---|---|---|---|---|---|---|---|---|---|
| 0x388b3d…d3fd9a | buyUpTo | 39,202,421 | 3 hrs agoMon, 17 Aug 2026 22:39:32 UTC | 0x45e2…5afc | IN | LootRouterV4 | $95.020.05 ETH | 0.00002113 | |
| 0x2294bc…6163b6 | sell | 39,198,792 | 3 hrs agoMon, 17 Aug 2026 22:33:28 UTC | 0x558d…7a5c | IN | LootRouterV4 | $0.000 ETH | 0.00000517 | |
| 0xf47e30…fb83fd | buy | 39,198,671 | 3 hrs agoMon, 17 Aug 2026 22:33:16 UTC | 0x558d…7a5c | IN | LootRouterV4 | $3.800.002 ETH | 0.00002178 | |
| 0x8ff0ec…299962 | buy | 39,197,784 | 3 hrs agoMon, 17 Aug 2026 22:31:47 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001347 | |
| 0x19efc2…d4f5f4 | sell | 39,197,207 | 3 hrs agoMon, 17 Aug 2026 22:30:49 UTC | 0x558d…7a5c | IN | LootRouterV4 | $0.000 ETH | 0.00000526 | |
| 0x163620…3bd83d | buy | 39,197,161 | 3 hrs agoMon, 17 Aug 2026 22:30:44 UTC | 0x558d…7a5c | IN | LootRouterV4 | $5.700.003 ETH | 0.00002136 | |
| 0x61d38b…be227a | buy | 39,177,937 | 4 hrs agoMon, 17 Aug 2026 21:58:36 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001325 | |
| 0x898f0a…00f35a | sell | 39,177,753 | 4 hrs agoMon, 17 Aug 2026 21:58:17 UTC | 0x558d…7a5c | IN | LootRouterV4 | $0.000 ETH | 0.00000617 | |
| 0xdbfdeb…622806 | buy | 39,177,553 | 4 hrs agoMon, 17 Aug 2026 21:57:57 UTC | 0x558d…7a5c | IN | LootRouterV4 | $7.600.004 ETH | 0.00001796 | |
| 0xff1aff…0823b8 | buy | 39,103,364 | 6 hrs agoMon, 17 Aug 2026 19:53:54 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001293 | |
| 0xf66fd4…9ded30 | buy | 39,096,495 | 6 hrs agoMon, 17 Aug 2026 19:42:25 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001282 | |
| 0xa0dfed…ce4054 | buy | 39,095,946 | 6 hrs agoMon, 17 Aug 2026 19:41:30 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001257 | |
| 0xda5488…811eab | buy | 39,094,595 | 6 hrs agoMon, 17 Aug 2026 19:39:15 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001279 | |
| 0x65e15c…d0944a | buy | 39,092,647 | 6 hrs agoMon, 17 Aug 2026 19:35:59 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001258 | |
| 0x788c40…489c9c | buy | 39,091,778 | 6 hrs agoMon, 17 Aug 2026 19:34:32 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001295 | |
| 0x5c3acb…b5bee9 | buy | 39,091,067 | 6 hrs agoMon, 17 Aug 2026 19:33:21 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001260 | |
| 0x37ab0f…f9b34a | buy | 39,083,053 | 6 hrs agoMon, 17 Aug 2026 19:19:57 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001285 | |
| 0xd5015e…ca0cb5 | buy | 39,082,470 | 6 hrs agoMon, 17 Aug 2026 19:18:59 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001258 | |
| 0xcbb698…4a7588 | buy | 39,082,391 | 6 hrs agoMon, 17 Aug 2026 19:18:51 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001286 | |
| 0x72256a…d9725c | buy | 39,079,311 | 6 hrs agoMon, 17 Aug 2026 19:13:42 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001262 | |
| 0x1680af…f19d2a | buy | 39,079,229 | 6 hrs agoMon, 17 Aug 2026 19:13:34 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001278 | |
| 0x83aa62…f7f3ec | buy | 39,078,265 | 6 hrs agoMon, 17 Aug 2026 19:11:57 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001259 | |
| 0x7a1395…011d37 | buy | 39,077,544 | 6 hrs agoMon, 17 Aug 2026 19:10:44 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001362 | |
| 0x8e959c…1158f9 | buy | 39,070,044 | 7 hrs agoMon, 17 Aug 2026 18:58:12 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001298 | |
| 0x84c31f…0f2ab8 | buy | 39,069,123 | 7 hrs agoMon, 17 Aug 2026 18:56:40 UTC | 0x5005…4b6a | IN | LootRouterV4 | $3.800.002 ETH | 0.00001273 |
| Block | Age | Parent Transaction Hash | Type | Method | From | To | Value | |
|---|---|---|---|---|---|---|---|---|
| 39,202,421 | 3 hrs agoMon, 17 Aug 2026 22:39:32 UTC | 0x388b3d…d3fd9a | CALL | buyUpTo | 0xfc0e…5668 | OUT | 0x45e2…5afc | 0.02596 ETH |
| 39,202,421 | 3 hrs agoMon, 17 Aug 2026 22:39:32 UTC | 0x388b3d…d3fd9a | CALL | buyUpTo | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.02403 ETH |
| 39,198,671 | 3 hrs agoMon, 17 Aug 2026 22:33:16 UTC | 0xf47e30…fb83fd | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,197,784 | 3 hrs agoMon, 17 Aug 2026 22:31:47 UTC | 0x8ff0ec…299962 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,197,161 | 3 hrs agoMon, 17 Aug 2026 22:30:44 UTC | 0x163620…3bd83d | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.003 ETH |
| 39,177,937 | 4 hrs agoMon, 17 Aug 2026 21:58:36 UTC | 0x61d38b…be227a | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,177,553 | 4 hrs agoMon, 17 Aug 2026 21:57:57 UTC | 0xdbfdeb…622806 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.004 ETH |
| 39,103,364 | 6 hrs agoMon, 17 Aug 2026 19:53:54 UTC | 0xff1aff…0823b8 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,096,495 | 6 hrs agoMon, 17 Aug 2026 19:42:25 UTC | 0xf66fd4…9ded30 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,095,946 | 6 hrs agoMon, 17 Aug 2026 19:41:30 UTC | 0xa0dfed…ce4054 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,094,595 | 6 hrs agoMon, 17 Aug 2026 19:39:15 UTC | 0xda5488…811eab | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,092,647 | 6 hrs agoMon, 17 Aug 2026 19:35:59 UTC | 0x65e15c…d0944a | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,091,778 | 6 hrs agoMon, 17 Aug 2026 19:34:32 UTC | 0x788c40…489c9c | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,091,067 | 6 hrs agoMon, 17 Aug 2026 19:33:21 UTC | 0x5c3acb…b5bee9 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,083,053 | 6 hrs agoMon, 17 Aug 2026 19:19:57 UTC | 0x37ab0f…f9b34a | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,082,470 | 6 hrs agoMon, 17 Aug 2026 19:18:59 UTC | 0xd5015e…ca0cb5 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,082,391 | 6 hrs agoMon, 17 Aug 2026 19:18:51 UTC | 0xcbb698…4a7588 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,079,311 | 6 hrs agoMon, 17 Aug 2026 19:13:42 UTC | 0x72256a…d9725c | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,079,229 | 6 hrs agoMon, 17 Aug 2026 19:13:34 UTC | 0x1680af…f19d2a | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,078,265 | 6 hrs agoMon, 17 Aug 2026 19:11:57 UTC | 0x83aa62…f7f3ec | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,077,544 | 6 hrs agoMon, 17 Aug 2026 19:10:44 UTC | 0x7a1395…011d37 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,070,044 | 7 hrs agoMon, 17 Aug 2026 18:58:12 UTC | 0x8e959c…1158f9 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,069,123 | 7 hrs agoMon, 17 Aug 2026 18:56:40 UTC | 0x84c31f…0f2ab8 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,068,867 | 7 hrs agoMon, 17 Aug 2026 18:56:15 UTC | 0xc9a928…3e7e04 | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |
| 39,066,084 | 7 hrs agoMon, 17 Aug 2026 18:51:35 UTC | 0x6613a6…1965eb | CALL | buy | 0xfc0e…5668 | OUT | 0x8366…0951 | 0.002 ETH |