InterChainRouterV3

Description:

Decentralized Finance (DeFi) protocol contract providing Swap, Factory functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
"
    },
    "@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}
"
    },
    "contracts/v3/IOrders.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

//CCTP STRUCT
struct CCTPParams {
    uint32 destinationDomain;      // offset 0x00
    uint256 minAmountUsdc;         // offset 0x20
    uint256 maxFee;                // offset 0x40
    uint32 minFinalityThreshold;   // offset 0x60
    uint32 crossChainFee;          // offset 0x80
}

//Msg. transmitter
interface IMessageTransmitter {
   function receiveMessage(bytes calldata message, bytes calldata attestation) external returns (bool);
}

// Minimal required for Interface MULTICALL
struct Call3Value {
    address target;
    bool allowFailure;
    uint256 value;
    bytes callData;
}

struct MulticallData {
    address multicallTarget;
    Call3Value[] calls; 
    address refundTo;
    address nftRecipient;
}

struct Result {
    bool success;
    bytes returnData;
}

interface IMulticall {
    function multicall(
        Call3Value[] calldata calls,
        address refundTo,
        address nftRecipient
    ) external payable returns (Result[] memory returnData);
}


interface IPermit2 {
    struct TokenPermissions {
        address token;
        uint256 amount;
    }

    struct PermitTransferFrom {
        TokenPermissions permitted;
        uint256 nonce;
        uint256 deadline;
    }

    struct SignatureTransferDetails {
        address to;
        uint256 requestedAmount;
    }

    function permitWitnessTransferFrom(
        PermitTransferFrom memory permit,
        SignatureTransferDetails calldata transferDetails,
        address owner,
        bytes32 witness,
        string calldata witnessTypeString,
        bytes calldata signature
    ) external;
}

interface ITokenMessenger {
    /// @notice CCTP V2: burn + deposit with optional fee and finality settings
    function depositForBurn(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient,
        address burnToken,
        bytes32 destinationCaller,
        uint256 maxFee,
        uint32 minFinalityThreshold
    ) external returns (uint64 nonce);

    /// @notice CCTP V2 hook-enabled variant
    function depositForBurnWithHook(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient,
        address burnToken,
        bytes32 destinationCaller,
        uint256 maxFee,
        uint32 minFinalityThreshold,
        bytes calldata hookData
    ) external returns (uint64 nonce);
}

interface IEIP712Types {
    struct OrderParameters {
        address srcAsset;
        address dstAsset;
        uint256 srcQuantity;
        uint256 dstQuantity;
        uint256 minQuantity;
        uint128 darkSalt;
    }
    
    struct SignedOrder {
        address sender;
        OrderParameters parameters;
        uint256 deadline;
        address target;
        address filler;
        string orderType;
        bytes[] customData;  
    }
    
    struct SigPayload {
        uint256 nonce;
        bytes signature;
    }


    //for single chain AND the final step of cross chain (i.e. multicall in recieveHook)
    event OrderFilled(
        bytes32 indexed orderUUID,
        string  orderType,
        address target,
        address filler,
        address srcAsset,
        address dstAsset,
        uint256 srcQuantity,
        uint256 dstQuantity
    );

    //For bridging X1 --> X2  i.e. CCTP, OFT, etc. 
    event OrderBridged(
        bytes32 indexed orderUUID,
        string  orderType,
        uint32  dstCID,
        address target, //whomever is getting paid out on dest chain
        address filler, //always be the person relaying
        address srcAsset, //asset on the source chain i.e. if going USDC on arb to USDC on eth this is the USDC-arb address
        address dstAsset,
        uint256 bridgeQuantity,
        uint256 maxFee
    );


    // Common errors
    error Expired();
    error Unauthorized();
    error InvalidCCTPParams();
    error InvalidType();
    error InsufficientOutput();
    error OrderHash(bytes32 h1, bytes32 h2); // just for debugging
    error CCTPRedemption(bytes reason);      // include revert data.
    error CCTPDepositFailed(bytes reason);   // include revert data.
    error PayoutFailed(bytes reason);
    error MEVprotect(uint256 minExpected, uint256 actual);
}"
    },
    "contracts/v3/Router.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "./IOrders.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract InterChainRouterV3 is IEIP712Types, ReentrancyGuard {
    IPermit2 immutable PERMIT2;
    ITokenMessenger immutable TOKEN_MESSENGER;
    IMessageTransmitter immutable MESSAGE_TRANSMITTER;
    address immutable USDC_ADDRESS;

    // EIP-712 types
    bytes32 public constant _ORDER_PARAMS_TYPEHASH = keccak256("OrderParameters(address srcAsset,address dstAsset,uint256 srcQuantity,uint256 dstQuantity,uint256 minQuantity,uint128 darkSalt)");
    bytes32 public constant _WITNESS_TYPEHASH = keccak256("SignedOrder(address sender,OrderParameters parameters,uint256 deadline,address target,address filler,string orderType,bytes[] customData)OrderParameters(address srcAsset,address dstAsset,uint256 srcQuantity,uint256 dstQuantity,uint256 minQuantity,uint128 darkSalt)");
    string public constant witnessTypeString = "SignedOrder witness)OrderParameters(address srcAsset,address dstAsset,uint256 srcQuantity,uint256 dstQuantity,uint256 minQuantity,uint128 darkSalt)SignedOrder(address sender,OrderParameters parameters,uint256 deadline,address target,address filler,string orderType,bytes[] customData)TokenPermissions(address token,uint256 amount)";
    
    // Pre-computed order type identifiers
    bytes32 public constant TYPE_DEX = keccak256("DEX");      //X1->Y1; Single Chain Dex swaps
    bytes32 public constant TYPE_RELAY = keccak256("CCTP");   //X1->USDC2 *X1 is possibly USDC; CCTP, swap into USDC cross chain
    bytes32 public constant TYPE_CROSS = keccak256("CROSS");  //X1->Y2 *X1 is possibly USDC; cross-chain swap into token 

    constructor(
        address _permit2,
        address _tokenMessenger,
        address _messageTransmitter,
        address _usdcAddress
    ) {
        PERMIT2 = IPermit2(_permit2);
        TOKEN_MESSENGER = ITokenMessenger(_tokenMessenger);
        MESSAGE_TRANSMITTER = IMessageTransmitter(_messageTransmitter);
        USDC_ADDRESS = _usdcAddress;
    }

    /// @notice fill a Single Chain Order (X1-->Y1)
    function fillOrder(
        SignedOrder calldata order,
        SigPayload calldata payload,
        MulticallData calldata arb,
        uint256 minOut
    ) external nonReentrant {
        //1. pull tokens
        _assertOrderRequirements(order, TYPE_DEX, true);
        bytes32 orderHash = pullTokens(order, payload);

        //2. Approve 
        IERC20(order.parameters.srcAsset).approve(arb.multicallTarget, order.parameters.srcQuantity);

        //3. Execute multicall data one shot
        IMulticall(arb.multicallTarget).multicall(arb.calls, arb.refundTo, arb.nftRecipient);

        //4. Verify swap amount
        if (!_payout(orderHash, order, order.parameters.dstAsset, order.parameters.minQuantity, minOut)) revert InsufficientOutput();
    }

    /// @notice send a Multichain Order (X1-->Y2)
    function sendOrder(
        SignedOrder calldata order,
        SigPayload calldata payload,
        MulticallData calldata arb,
        uint256 minOut
    ) external nonReentrant {
        // pull funds
        bytes32 orderHash = pullTokens(order, payload);
        uint256 balance=IERC20(USDC_ADDRESS).balanceOf(address(this));
        CCTPParams memory cctp = _cacheCCTPParams(order.customData);
        
        // Make optional Swap
        if (order.parameters.srcAsset!=USDC_ADDRESS){
            //Approve + Execute Mutlicall
            IERC20(order.parameters.srcAsset).approve(arb.multicallTarget, order.parameters.srcQuantity);
            IMulticall(arb.multicallTarget).multicall(arb.calls, arb.refundTo, arb.nftRecipient);
            balance=IERC20(USDC_ADDRESS).balanceOf(address(this));
            if (balance<cctp.minAmountUsdc) revert InsufficientOutput();
            if (balance<minOut) revert MEVprotect(minOut, balance);
        }
        else{if (balance<cctp.minAmountUsdc) revert InsufficientOutput();}

        // Approve messenger to spend usdc
        IERC20(USDC_ADDRESS).approve(address(TOKEN_MESSENGER), balance);
        
        // Send to CCTP v2 wowo Hook
        if (keccak256(bytes(order.orderType))==TYPE_CROSS) {
            _assertOrderRequirements(order, TYPE_CROSS, true);

            (bool success, bytes memory data) = address(TOKEN_MESSENGER).call(
                abi.encodeWithSelector(
                    ITokenMessenger.depositForBurnWithHook.selector,
                    balance,
                    cctp.destinationDomain,
                    _addressToBytes32(address(this)), //  <--- Destination Router has to be the receiver
                    USDC_ADDRESS,
                    _addressToBytes32(address(this)),
                    cctp.maxFee,
                    cctp.minFinalityThreshold,
                    abi.encodePacked(orderHash)
                )
            );

            if (!success) revert CCTPDepositFailed(data);
        }

        else {
            _assertOrderRequirements(order, TYPE_RELAY, true);
            if (order.parameters.srcAsset==USDC_ADDRESS) {
                //take fee
                uint256 fee = order.parameters.srcQuantity - order.parameters.dstQuantity;
                balance-=fee;
                IERC20(order.parameters.srcAsset).transfer(order.filler, fee);
            }

            (bool success, bytes memory data) = address(TOKEN_MESSENGER).call(
                abi.encodeWithSelector(
                    ITokenMessenger.depositForBurn.selector,
                    balance,
                    cctp.destinationDomain,
                    _addressToBytes32(order.target), //  <--- Destination actual recipient
                    USDC_ADDRESS,
                    _addressToBytes32(address(0)),
                    cctp.maxFee,
                    cctp.minFinalityThreshold
                )
            );

            if (!success) revert CCTPDepositFailed(data);
        }

        emit OrderBridged(
            orderHash,
            order.orderType,
            cctp.destinationDomain,
            order.target,
            order.filler,
            order.parameters.srcAsset,
            order.parameters.dstAsset,
            balance,
            cctp.maxFee
        );

    }

    event Balances(uint256 ethBalance, bool ok, uint256 usdcReceived, uint256 minQuantity);

    event ApprovalFailed(address token, address spender, uint256 amount);
    event MulticallFailed(address target);

    /// @notice recieve a Multichain Order (X1-->Y2)
    function takeOrder(
        bytes calldata message,
        bytes calldata attestation,
        SignedOrder calldata order,
        MulticallData calldata arb,
        uint256 minOut
    ) external nonReentrant returns(bool fin) {
        
        // 1. Verify order
        bytes32 orderHash = _extractHookData(message);
        if (hashOrder(order) != orderHash) revert OrderHash(hashOrder(order), orderHash);
        _assertOrderRequirements(order, TYPE_CROSS, false);

        //2. Mint USDC via Iris
        (bool success, bytes memory data) = address(MESSAGE_TRANSMITTER).call(
            abi.encodeWithSelector(
                IMessageTransmitter.receiveMessage.selector,
                message,
                attestation
            )
        );
        if (!success) revert CCTPRedemption(data);

        uint256 usdcReceived = IERC20(USDC_ADDRESS).balanceOf(address(this));

        // 3. Approve Multicall
        (bool approveOk, bytes memory approveRet) = USDC_ADDRESS.call(
                abi.encodeWithSelector(
                IERC20.approve.selector,
                arb.multicallTarget,
                usdcReceived
            )
        );
        if (!(approveOk && (approveRet.length == 0 || abi.decode(approveRet, (bool))))) {
            emit ApprovalFailed(USDC_ADDRESS, arb.multicallTarget, order.parameters.srcQuantity);
        }

        (bool multicallOk, ) = arb.multicallTarget.call(
            abi.encodeWithSelector(
                IMulticall.multicall.selector,
                arb.calls,
                arb.refundTo,
                arb.nftRecipient
            )
        );
        if (!multicallOk) {
            emit MulticallFailed(arb.multicallTarget);
        }
        // 5. Attempt User Payout
        if (_payout(orderHash, order, order.parameters.dstAsset, order.parameters.minQuantity, minOut)) {
            return true;
        }
        
        if (_payout(orderHash, order, USDC_ADDRESS, usdcReceived, minOut)) {
            return false;
        }

        revert InsufficientOutput();
    }
    
    //*HELPER FUNCTIONS*//
    function pullTokens(
        SignedOrder calldata order,
        SigPayload calldata payload
    ) private returns (bytes32 orderHash) {
        //TO DO must make EIP-712 compliant
        orderHash = hashOrder(order);
        
        PERMIT2.permitWitnessTransferFrom(
            IPermit2.PermitTransferFrom({
                permitted: IPermit2.TokenPermissions({
                    token: order.parameters.srcAsset,
                    amount: order.parameters.srcQuantity
                }),
                nonce: payload.nonce,
                deadline: order.deadline
            }),
            IPermit2.SignatureTransferDetails({
                to: address(this),
                requestedAmount: order.parameters.srcQuantity
            }),
            order.sender,
            orderHash,
            witnessTypeString,
            payload.signature
        );

        return orderHash;
    }
    
    function _payout(
        bytes32 orderHash,
        SignedOrder calldata order,
        address payoutAsset,
        uint256 minQuantity, 
        uint256 mevOut
    ) private returns (bool) {
        uint256 balance;

        if (payoutAsset == address(0)) {
            // ETH case
            balance = address(this).balance;
            if (balance < minQuantity) { return false; }
            if (balance < mevOut) revert MEVprotect(mevOut, balance);
            (bool ok,) = order.target.call{value: balance}("");
            if (!ok) {return ok;}
        } else {
            // ERC20 case – use low-level calls for broader token compatibility
            (bool balOk, bytes memory balData) = payoutAsset.staticcall(
                abi.encodeWithSelector(IERC20.balanceOf.selector, address(this))
            );
            if (!(balOk && balData.length >= 32)) {
                return false; // could not fetch balance safely
            }
            balance = abi.decode(balData, (uint256));
            if (balance < minQuantity) { return false; }
            if (balance < mevOut) revert MEVprotect(mevOut, balance);

            (bool txOk, bytes memory txData) = payoutAsset.call(
                abi.encodeWithSelector(IERC20.transfer.selector, order.target, balance)
            );
            bool transferred = txOk && (txData.length == 0 || abi.decode(txData, (bool)));
            if (!transferred) {
                revert PayoutFailed(txData);
            }
        }

        emit OrderFilled(
            orderHash,
            order.orderType,
            order.target,
            order.filler,
            order.parameters.srcAsset,
            payoutAsset,
            order.parameters.srcQuantity,
            balance
        );

        return true;
    }

    // EIP-712 compliant hashing
    function hashOrder(SignedOrder calldata o) public pure returns (bytes32) {
        bytes32 paramsHash = _hashParams(o.parameters);
        
        return keccak256(
            abi.encode(
                _WITNESS_TYPEHASH,
                o.sender,
                paramsHash,
                o.deadline,
                o.target,
                o.filler,
                keccak256(bytes(o.orderType)),
                _hashCustomDatas(o.customData)
            )
        );
    }

    // EIP-712 compliant hashing
    function _hashParams(OrderParameters calldata params) public pure returns (bytes32) {
        return keccak256(
            abi.encode(
                _ORDER_PARAMS_TYPEHASH,
                params.srcAsset,
                params.dstAsset,
                params.srcQuantity,
                params.dstQuantity,
                params.minQuantity,
                params.darkSalt
            )
        );
    }

    // EIP-712 compliant hashing
    function _hashCustomDatas(bytes[] calldata datas) internal pure returns (bytes32) {
        uint256 len = datas.length;
        bytes32[] memory inner = new bytes32[](len);
        for (uint256 i; i < len; ++i) {
            inner[i] = keccak256(datas[i]);
        }
        return keccak256(abi.encodePacked(inner));
    }

    function _assertOrderRequirements(
        SignedOrder calldata order,
        bytes32 requiredType,
        bool time_in_force
    ) internal view {
        if (time_in_force) {
            if (order.deadline < block.timestamp) revert Expired();
        }
        if (order.filler != address(0) && order.filler != msg.sender) {
            revert Unauthorized();
        }
        if (keccak256(bytes(order.orderType)) != requiredType) {
            revert InvalidType();
        }
    }

    function _cacheCCTPParams(bytes[] calldata customData) internal pure returns (CCTPParams memory params) {
        // Require 5 elements for CCTP parameters
        if (customData.length != 5) {revert InvalidCCTPParams();}
        assembly {
            // customData.offset is the start of the dynamic array's "head" in calldata
            
            // 1) destinationDomain (uint32) from customData[0]
            let data0 := add(calldataload(add(customData.offset, 0x00)), customData.offset)
            let raw0 := calldataload(add(data0, 0x20))
            mstore(params, shr(224, raw0))              // store at params + 0x00
            
            // 2) minAmountUsdc (uint256) from customData[1] - NEW FIELD IN SECOND POSITION
            let data1 := add(calldataload(add(customData.offset, 0x20)), customData.offset)
            let raw1 := calldataload(add(data1, 0x20))
            mstore(add(params, 0x20), raw1)             // store at params + 0x20
            
            // 3) maxFee (uint256) from customData[2]
            let data2 := add(calldataload(add(customData.offset, 0x40)), customData.offset)
            let raw2 := calldataload(add(data2, 0x20))
            mstore(add(params, 0x40), raw2)             // store at params + 0x40
            
            // 4) minFinalityThreshold (uint32) from customData[3]
            let data3 := add(calldataload(add(customData.offset, 0x60)), customData.offset)
            let raw3 := calldataload(add(data3, 0x20))
            mstore(add(params, 0x60), shr(224, raw3))   // store at params + 0x60
            
            // 5) crossChainFee (uint32) from customData[4]
            let data4 := add(calldataload(add(customData.offset, 0x80)), customData.offset)
            let raw4 := calldataload(add(data4, 0x20))
            mstore(add(params, 0x80), shr(224, raw4))   // store at params + 0x80
        }
    }


    function _extractHookData(bytes calldata message) internal pure returns (bytes32 orderHash) {
        require(message.length >= 32, "message too short");

        // hookData is the last 32 bytes regardless of message length
        // TOKEN_MESSENGER uses abi.encodeWithSelector to encode the message which makes
        assembly {
            //  ptr = message.offset + message.length - 32
            orderHash := calldataload(
                sub(add(message.offset, message.length), 32)
            )
        }
    }

    function _addressToBytes32(address _addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(_addr)));
    }

    // Receive ETH from multicall swaps
    receive() external payable {}
}"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 600
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}}

Tags:
ERC20, DeFi, Swap, Factory|addr:0x888888863411444403aa85df4198c0176cefc2b2|verified:true|block:23383149|tx:0xb55a95cced381068da4a10f71d9f5bf133021b3b5c2bb5d52e6d4384dce5903b|first_check:1758119914

Submitted on: 2025-09-17 16:38:35

Comments

Log in to comment.

No comments yet.