ZapV3

0xfef4bfb71d007ae8bfb63d415a6e56458c2412a0

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
12,683 bytes
ABI entries
60
7 read · 13 write
License
none

Contract information

Address
0xfef4bfb71d007ae8bfb63d415a6e56458c2412a0
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x5b1b67e0b7…1749423923

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (7)

MAX_POSITIONS()uint256
WRAPPED_NATIVE()address
owner()address
paused()bool
pendingOwner()address
positionManagerWhitelist(address)bool
routerWhitelist(address)bool

Events (11)

LadderCreatedOwnershipTransferStartedOwnershipTransferredPausedPositionManagerSetRescuedRescuedETHRouterSetUnpausedWrappedNativeSetZapped

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "initialOwner",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadySet",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "actual",
        "type": "uint256"
      }
    ],
    "name": "AmountMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token0",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token1",
        "type": "address"
      }
    ],
    "name": "BadTokenOrder",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "blockTimestamp",
        "type": "uint256"
      }
    ],
    "name": "DeadlineExpired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmptyLadder",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EnforcedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExpectedPause",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FailedCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "needed",
        "type": "uint256"
      }
    ],
    "name": "InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "received",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minRequired",
        "type": "uint256"
      }
    ],
    "name": "InsufficientOutput",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "pulled",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "swapAmount",
        "type": "uint256"
      }
    ],
    "name": "InsufficientPulledForSwap",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotImplemented",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "StableMustBeWrappedNative",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "swapAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "stableAmount",
        "type": "uint256"
      }
    ],
    "name": "SwapAmountExceedsTotal",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "reason",
        "type": "bytes"
      }
    ],
    "name": "SwapCallFailed",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "stable",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "volatile",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token0",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token1",
        "type": "address"
      }
    ],
    "name": "TokensMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "count",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "max",
        "type": "uint256"
      }
    ],
    "name": "TooManyPositions",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pm",
        "type": "address"
      }
    ],
    "name": "UnauthorizedPM",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "router",
        "type": "address"
      }
    ],
    "name": "UnauthorizedRouter",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "WrappedNativeNotSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "ZeroLiquidityMinted",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "user",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256[]",
        "name": "tokenIds",
        "type": "uint256[]"
      }
    ],
    "name": "LadderCreated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "pm",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      }
    ],
    "name": "PositionManagerSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "Rescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "RescuedETH",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "router",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      }
    ],
    "name": "RouterSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "wn",
        "type": "address"
      }
    ],
    "name": "WrappedNativeSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "user",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "stable",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "volatile",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "stableSpent",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "volatileReceived",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "positions",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint8",
        "name": "zapMode",
        "type": "uint8"
      }
    ],
    "name": "Zapped",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_POSITIONS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WRAPPED_NATIVE",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "positionManagerWhitelist",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IERC20",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescue",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address payable",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueETH",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "routerWhitelist",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "pm",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      }
    ],
    "name": "setPositionManager",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "router",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "allowed",
        "type": "bool"
      }
    ],
    "name": "setRouter",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "wn",
        "type": "address"
      }
    ],
    "name": "setWrappedNative",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "stable",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "volatile",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "stableAmount",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "swapAmount",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "minVolatileOut",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "okxRouter",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "okxApproveTarget",
            "type": "address"
          },
          {
            "internalType": "bytes",
            "name": "okxCalldata",
            "type": "bytes"
          },
          {
            "internalType": "address",
            "name": "positionManager",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token0",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          },
          {
            "components": [
              {
                "internalType": "int24",
                "name": "tickLower",
                "type": "int24"
              },
              {
                "internalType": "int24",
                "name": "tickUpper",
                "type": "int24"
              },
              {
                "internalType": "uint256",
                "name": "amount0",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount1",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount0Min",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount1Min",
                "type": "uint256"
              }
            ],
            "internalType": "struct ZapV3.Position[]",
            "name": "positions",
            "type": "tuple[]"
          }
        ],
        "internalType": "struct ZapV3.ZapParams",
        "name": "p",
        "type": "tuple"
      }
    ],
    "name": "zapIn",
    "outputs": [
      {
        "internalType": "uint256[]",
        "name": "tokenIds",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "stable",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "volatile",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "stableAmount",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "swapAmount",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "minVolatileOut",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "okxRouter",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "okxApproveTarget",
            "type": "address"
          },
          {
            "internalType": "bytes",
            "name": "okxCalldata",
            "type": "bytes"
          },
          {
            "internalType": "address",
            "name": "positionManager",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token0",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          },
          {
            "components": [
              {
                "internalType": "int24",
                "name": "tickLower",
                "type": "int24"
              },
              {
                "internalType": "int24",
                "name": "tickUpper",
                "type": "int24"
              },
              {
                "internalType": "uint256",
                "name": "amount0",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount1",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount0Min",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount1Min",
                "type": "uint256"
              }
            ],
            "internalType": "struct ZapV3.Position[]",
            "name": "positions",
            "type": "tuple[]"
          }
        ],
        "internalType": "struct ZapV3.ZapParams",
        "name": "p",
        "type": "tuple"
      },
      {
        "internalType": "uint256",
        "name": "extraVolatileAmount",
        "type": "uint256"
      }
    ],
    "name": "zapInDual",
    "outputs": [
      {
        "internalType": "uint256[]",
        "name": "tokenIds",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "stable",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "volatile",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "stableAmount",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "swapAmount",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "minVolatileOut",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "okxRouter",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "okxApproveTarget",
            "type": "address"
          },
          {
            "internalType": "bytes",
            "name": "okxCalldata",
            "type": "bytes"
          },
          {
            "internalType": "address",
            "name": "positionManager",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token0",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          },
          {
            "components": [
              {
                "internalType": "int24",
                "name": "tickLower",
                "type": "int24"
              },
              {
                "internalType": "int24",
                "name": "tickUpper",
                "type": "int24"
              },
              {
                "internalType": "uint256",
                "name": "amount0",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount1",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount0Min",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "amount1Min",
                "type": "uint256"
              }
            ],
            "internalType": "struct ZapV3.Position[]",
            "name": "positions",
            "type": "tuple[]"
          }
        ],
        "internalType": "struct ZapV3.ZapParams",
        "name": "p",
        "type": "tuple"
      }
    ],
    "name": "zapInWithNative",
    "outputs": [
      {
        "internalType": "uint256[]",
        "name": "tokenIds",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {ZapBase} from "./ZapBase.sol";
import {INonfungiblePositionManager as IPM} from "./interfaces/INonfungiblePositionManager.sol";

/// @title ZapV3 (v2) — atomic swap + V3 ladder mint with balance-based distribution.
///
/// @notice One TX: pull stable from user (or wrap native), optionally swap a
///         portion to volatile, then mint N V3 position NFTs to the user.
///
/// @dev    v2 changes vs v1 (see ZAP_PLAN.md):
///           - Balance-based budget: read post-swap balance delta and use it
///             as the authoritative basis. Backend stops bumping caps.
///           - Proportional distribution: per-position
///             `amount{0,1}Desired = floor(budget × raw_i / Σ raw)`; last
///             position absorbs the floor-div remainder. PM consumes only
///             what liquidity needs at current tick; leftover refunded.
///             `amount{0,1}Min` retained (backend sets 0).
///           - Three entry points: `zapIn` (ERC20), `zapInWithNative`
///             (wraps `msg.value`), `zapInDual` (functional, dormant until
///             backend ships). Native refunds unwrap WBNB → raw native.
///
///         V3 has no revert class to fix (uses `amountMin = 0`, no Permit2),
///         but suffers the SAME backend-bump dust pattern as V4. Bringing
///         V3 into the v2 design reduces dust from ~1-2% of user budget to
///         the floor-div remainder + aggregator overfill (~0.1-0.5%).
contract ZapV3 is ZapBase {
    using SafeERC20 for IERC20;

    /// @dev Per-position V3 mint recipe. `amount0`/`amount1` are RAW weights
    ///      from the backend distribution math — used here to scale a
    ///      per-position share of the balance-based budget. `amount0Min`/
    ///      `amount1Min` are retained (backend sets 0; defensive sanity, no
    ///      slippage enforcement at mint level).
    struct Position {
        int24 tickLower;
        int24 tickUpper;
        uint256 amount0;
        uint256 amount1;
        uint256 amount0Min;
        uint256 amount1Min;
    }

    struct ZapParams {
        // ── Swap leg ──
        address stable;
        address volatile;
        uint256 stableAmount;
        uint256 swapAmount;
        uint256 minVolatileOut;
        address okxRouter;
        address okxApproveTarget;
        bytes okxCalldata;
        // ── Mint leg ──
        address positionManager;
        address token0;
        address token1;
        uint24 fee;
        uint256 deadline;
        Position[] positions;
    }

    error SwapAmountExceedsTotal(uint256 swapAmount, uint256 stableAmount);
    error TokensMismatch(address stable, address volatile, address token0, address token1);
    error BadTokenOrder(address token0, address token1);
    error InsufficientPulledForSwap(uint256 pulled, uint256 swapAmount);
    error ZeroLiquidityMinted(uint256 tokenId);

    event LadderCreated(address indexed user, uint256[] tokenIds);

    constructor(address initialOwner) ZapBase(initialOwner) {}

    // ─── Entry points ─────────────────────────────────────────────
    /// @notice Standard ERC20 zap. msg.value MUST be 0 (non-payable).
    function zapIn(ZapParams calldata p)
        external
        nonReentrant
        whenNotPaused
        returns (uint256[] memory tokenIds)
    {
        return _zapInInternal(p, false, 0);
    }

    /// @notice Native zap. Wraps `msg.value` to WETH/WBNB; native refund on exit.
    function zapInWithNative(ZapParams calldata p)
        external
        payable
        nonReentrant
        whenNotPaused
        returns (uint256[] memory tokenIds)
    {
        if (msg.value == 0) revert ZeroAmount();
        return _zapInInternal(p, true, 0);
    }

    /// @notice Dual zap. Pulls `extraVolatileAmount` of volatile from user
    ///         in addition to stable. Swap leg may be smaller or skipped.
    /// @dev    Native dual is detected by `msg.value > 0`. Audited as part
    ///         of v2; backend / frontend remain unwired in v1 of v2.
    function zapInDual(ZapParams calldata p, uint256 extraVolatileAmount)
        external
        payable
        nonReentrant
        whenNotPaused
        returns (uint256[] memory tokenIds)
    {
        if (extraVolatileAmount == 0) revert ZeroAmount();
        return _zapInInternal(p, msg.value > 0, extraVolatileAmount);
    }

    // ─── Core flow ────────────────────────────────────────────────
    function _zapInInternal(ZapParams calldata p, bool useNative, uint256 extraVolatile)
        internal
        returns (uint256[] memory tokenIds)
    {
        // Native-mode-specific guards fire FIRST so the user gets the most
        // actionable error: a misconfigured native zap (WN unset, or stable
        // ≠ WBNB) shouldn't surface as the generic TokensMismatch from
        // downstream validation. `_acquireStable` retains the same checks as
        // defense-in-depth.
        if (useNative) {
            if (WRAPPED_NATIVE == address(0)) revert WrappedNativeNotSet();
            if (p.stable != WRAPPED_NATIVE) revert StableMustBeWrappedNative();
        }

        // ── Input validation (fail-fast, before any state change) ──
        if (block.timestamp > p.deadline) revert DeadlineExpired(p.deadline, block.timestamp);
        uint256 n = p.positions.length;
        if (n == 0) revert EmptyLadder();
        if (n > MAX_POSITIONS) revert TooManyPositions(n, MAX_POSITIONS);
        if (p.stableAmount == 0) revert ZeroAmount();
        // swapAmount may legitimately be 0 only in dual mode (extraVolatile > 0).
        if (p.swapAmount == 0 && extraVolatile == 0) revert ZeroAmount();
        if (p.swapAmount > p.stableAmount) {
            revert SwapAmountExceedsTotal(p.swapAmount, p.stableAmount);
        }
        if (p.stable == address(0) || p.volatile == address(0)) revert ZeroAddress();
        if (p.stable == p.volatile) revert TokensMismatch(p.stable, p.volatile, p.token0, p.token1);
        if (!positionManagerWhitelist[p.positionManager]) revert UnauthorizedPM(p.positionManager);

        if (p.token0 >= p.token1) revert BadTokenOrder(p.token0, p.token1);
        // Single-line set match — saves a stack slot vs two named bools, which
        // matters under via_ir=false. The compiler doesn't always reap dead
        // bool locals between SSA blocks; we don't gain anything by naming.
        if (
            !(p.token0 == p.stable && p.token1 == p.volatile)
                && !(p.token0 == p.volatile && p.token1 == p.stable)
        ) {
            revert TokensMismatch(p.stable, p.volatile, p.token0, p.token1);
        }

        IERC20 stable = IERC20(p.stable);
        IERC20 volatile = IERC20(p.volatile);

        // Snapshot pre-existing balances — done BEFORE any acquisition so
        // wrap's WBNB credit shows up as user delta, not stuck dust.
        uint256 stableBefore = stable.balanceOf(address(this));
        uint256 volBefore = volatile.balanceOf(address(this));

        // Reentry into public surface blocked by `nonReentrant`. `WrappedNative.deposit`
        // (native) / `safeTransferFrom` of whitelisted stable (ERC20) — neither
        // callbacks into msg.sender. `pulled` is RECOMPUTED post-call below, not
        // reused. Slither's `reentrancy-balance` acknowledged (v1 audit I-6 pattern).
        // slither-disable-next-line reentrancy-balance
        _acquireStable(stable, p.stableAmount, useNative);
        uint256 pulled = stable.balanceOf(address(this)) - stableBefore;
        if (pulled < p.swapAmount) revert InsufficientPulledForSwap(pulled, p.swapAmount);

        if (extraVolatile > 0) {
            volatile.safeTransferFrom(msg.sender, address(this), extraVolatile);
        }

        uint256 volReceived = 0;
        {
            if (p.swapAmount > 0) {
                (volReceived,) = _executeSwap(
                    SwapCall({
                        tokenIn: stable,
                        tokenOut: volatile,
                        maxAmountIn: p.swapAmount,
                        minAmountOut: p.minVolatileOut,
                        router: p.okxRouter,
                        approveTarget: p.okxApproveTarget
                    }),
                    p.okxCalldata
                );
            }

            // Balance-based budget per canonical-order leg. Inlined branch
            // (instead of declaring named budget0/budget1 locals) keeps the
            // stack within the 16-slot limit under via_ir=false.
            uint256 stableForMint = stable.balanceOf(address(this)) - stableBefore;
            uint256 volForMint = volatile.balanceOf(address(this)) - volBefore;
            tokenIds = (p.token0 == p.stable)
                ? _mintLadder(p, stableForMint, volForMint)
                : _mintLadder(p, volForMint, stableForMint);
        }

        // Refund leftover only — V3 PM consumes only what liquidity needs at
        // current tick, so per-position underspend accumulates here. Native
        // mode unwraps WBNB → raw native via `_refundStable`.
        {
            uint256 stableLeft = stable.balanceOf(address(this)) - stableBefore;
            _refundStable(stable, stableLeft, useNative);
        }
        {
            uint256 volLeft = volatile.balanceOf(address(this)) - volBefore;
            if (volLeft > 0) volatile.safeTransfer(msg.sender, volLeft);
        }

        uint8 zapMode = extraVolatile > 0 ? 2 : (useNative ? 1 : 0);
        emit Zapped(msg.sender, p.stable, p.volatile, pulled, volReceived, n, zapMode);
        emit LadderCreated(msg.sender, tokenIds);
    }

    /// @dev Approve PM up to the user's balance-based budget on each side,
    ///      distribute proportionally per RAW position weight (last position
    ///      absorbs the floor-div remainder), mint, then revoke approvals.
    ///
    ///      Distribution formula:
    ///         desired0_i = floor(budget0 × raw0_i / Σ raw0)   (last: remainder)
    ///         desired1_i = floor(budget1 × raw1_i / Σ raw1)   (last: remainder)
    ///
    ///      With `amount{0,1}Min = 0` (backend default), the PM consumes ≤
    ///      desired and never reverts on under-fill — leftover stays on the
    ///      contract for refund. Σ desired_i ≤ budget by construction, so
    ///      the per-token approval cap is always sufficient.
    ///
    ///      The mint loop is split into helpers (`_shareOf`, `_mintOne`) so
    ///      multiplication / `IPM.mint` parameter packing happen in their
    ///      own stack frames — keeps `_mintLadder` itself within the EVM
    ///      stack limit under via_ir=false.
    function _mintLadder(ZapParams calldata p, uint256 budget0, uint256 budget1)
        internal
        returns (uint256[] memory tokenIds)
    {
        // Approve PM at the EXACT balance-based budget — capped at user
        // contribution, so PM cannot consume any pre-existing stuck balance.
        IERC20(p.token0).forceApprove(p.positionManager, budget0);
        IERC20(p.token1).forceApprove(p.positionManager, budget1);

        uint256 n = p.positions.length;
        tokenIds = new uint256[](n);

        // Σ raw weights — denominator for the proportional split.
        uint256 sumRaw0 = 0;
        uint256 sumRaw1 = 0;
        for (uint256 i; i < n;) {
            sumRaw0 += p.positions[i].amount0;
            sumRaw1 += p.positions[i].amount1;
            unchecked {
                ++i;
            }
        }

        _runMintLoop(p, budget0, budget1, sumRaw0, sumRaw1, tokenIds);

        IERC20(p.token0).forceApprove(p.positionManager, 0);
        IERC20(p.token1).forceApprove(p.positionManager, 0);
    }

    /// @dev Per-position desire calculation. Extracted so the multiplication
    ///      temporary lives in this frame, not the caller's.
    function _shareOf(uint256 budget, uint256 raw, uint256 sumRaw) internal pure returns (uint256) {
        // Σ raw == 0 → 0 for non-last; last position absorbs remainder, so
        // a one-sided ladder still settles correctly through the caller.
        if (sumRaw == 0) return 0;
        return (budget * raw) / sumRaw;
    }

    /// @dev Distribute (budget0, budget1) across all positions and mint each.
    ///      Caller (`_mintLadder`) has already approved PM and computed sums.
    ///      Mutates `tokenIds` in place (passed as memory pointer to avoid
    ///      a second return-allocation).
    function _runMintLoop(
        ZapParams calldata p,
        uint256 budget0,
        uint256 budget1,
        uint256 sumRaw0,
        uint256 sumRaw1,
        uint256[] memory tokenIds
    ) internal {
        uint256 n = p.positions.length;
        uint256 distributed0 = 0;
        uint256 distributed1 = 0;
        for (uint256 i; i < n;) {
            uint256 desired0;
            uint256 desired1;
            if (i == n - 1) {
                // Last position absorbs the floor-div remainder so
                // Σ desired == budget exactly. Underflow-safe: distributed
                // is the running sum of prior floor-divs ≤ budget.
                desired0 = budget0 - distributed0;
                desired1 = budget1 - distributed1;
            } else {
                desired0 = _shareOf(budget0, p.positions[i].amount0, sumRaw0);
                desired1 = _shareOf(budget1, p.positions[i].amount1, sumRaw1);
                distributed0 += desired0;
                distributed1 += desired1;
            }
            tokenIds[i] = _mintOne(p, i, desired0, desired1);
            unchecked {
                ++i;
            }
        }
    }

    /// @dev Single-position mint. Extracted so the `IPM.MintParams` struct
    ///      packing happens in its own stack frame.
    function _mintOne(ZapParams calldata p, uint256 i, uint256 desired0, uint256 desired1)
        internal
        returns (uint256 tid)
    {
        Position calldata pos = p.positions[i];
        uint128 liquidity;
        // slither-disable-next-line unused-return
        (tid, liquidity,,) = IPM(p.positionManager).mint(
            IPM.MintParams({
                token0: p.token0,
                token1: p.token1,
                fee: p.fee,
                tickLower: pos.tickLower,
                tickUpper: pos.tickUpper,
                amount0Desired: desired0,
                amount1Desired: desired1,
                amount0Min: pos.amount0Min,
                amount1Min: pos.amount1Min,
                recipient: msg.sender,
                deadline: p.deadline
            })
        );
        // A V3 mint that returns 0 liquidity is either a buggy PM or a
        // pool/tick combo that produced an effectively-empty position —
        // either way the user paid gas and tokens-out for an NFT with no
        // LP value. Fail loud and roll the whole zap back. Slither's
        // `incorrect-equality` is FP — sentinel 0 from whitelisted PM.
        // slither-disable-next-line incorrect-equality
        if (liquidity == 0) revert ZeroLiquidityMinted(tid);
    }
}
Chain explorer2853msChain node82ms