SecondaryMarketFactory

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "market/SecondaryMarketFactory.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import "./SecondaryMarket.sol";\r
\r
contract SecondaryMarketFactory {\r
\r
    address public constant REACTOR = address(0x2A48D7aaF58C993943c192b582dbCeFBF674748B); // TODO: set the reactor address here\r
    bytes32 public constant SALT = bytes32(uint256(21092025));\r
\r
    event SecondaryMarketDeployed(address indexed owner, address market, address router);\r
\r
    /**\r
     * @notice Deploys a new SecondaryMarket contract using CREATE2\r
     * @param owner The owner of the SecondaryMarket\r
     * @param router The router address\r
     * @return market The address of the deployed SecondaryMarket\r
     */\r
    function deploy(address owner, address router, address currency) external returns (address) {\r
        SecondaryMarket market = new SecondaryMarket{salt: SALT}(owner, REACTOR, router, currency);\r
        emit SecondaryMarketDeployed(owner, address(market), router);\r
        return address(market);\r
    }\r
\r
    /**\r
     * @notice Predicts the address where a SecondaryMarket will be deployed\r
     * @param owner The owner of the SecondaryMarket\r
     * @param router The router address\r
     * @return predicted The predicted deployment address\r
     */\r
    function predict(address owner, address router, address currency) external view returns (address predicted) {\r
        bytes memory initCode = abi.encodePacked(type(SecondaryMarket).creationCode, abi.encode(owner, REACTOR, router, currency));\r
        bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), SALT, keccak256(initCode)));\r
        return address(uint160(uint256(hash)));\r
    }\r
\r
    /**\r
     * @notice Checks if a SecondaryMarket is already deployed at the predicted address\r
     * @param owner The owner of the SecondaryMarket\r
     * @param router The router address\r
     * @return isDeployed True if a contract exists at the predicted address\r
     */\r
    function isDeployed(address owner, address router, address currency) external view returns (bool) {\r
        address predictedAddress = this.predict(owner, router, currency);\r
        uint32 size;\r
        assembly {\r
            size := extcodesize(predictedAddress)\r
        }\r
        return size > 0;\r
    }\r
}"
    },
    "market/SecondaryMarket.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import "./EIP712.sol";\r
import "../ERC20/IERC20.sol";\r
import "../utils/Ownable.sol";\r
import {IReactor} from "./IReactor.sol";\r
import {ISignatureTransfer} from "./ISignatureTransfer.sol";\r
import {Intent} from "./IntentHash.sol";\r
\r
contract SecondaryMarket is Ownable {\r
\r
    uint16 public constant ALL = 10000;\r
    address public immutable REACTOR;\r
    address public immutable CURRENCY;\r
    address public constant LICENSE_FEE_RECIPIENT = 0x29Fe8914e76da5cE2d90De98a64d0055f199d06D;\r
\r
    event TradingFeeCollected(address currency, uint256 actualFee, address spreadRecipient, uint256 returnedSpread);\r
    event TradingFeeWithdrawn(address currency, address target, uint256 amount);\r
    event LicenseFeePaid(address currency, address target, uint256 amount);\r
    event Trade(address indexed seller, address indexed buyer, address token, uint256 tokenAmount, address currency, uint256 currencyAmount, uint256 fees);\r
\r
    error LargerSpreadNeeded(uint256 feesCollected, uint256 requiredMinimum);\r
    error WrongFiller();\r
    error WrongRouter(address expected, address actual);\r
    error InvalidConfiguration();\r
    error SignatureExpired(uint256 signatureDeadline);\r
    error MarketClosed(uint256 openFrom, uint256 openTo, uint256 nowTime);\r
    error NoBalance(address token, address owner);\r
    error NoAllowance(address token, address owner, address spender);\r
    error NonceUsed(address owner, uint256 nonce);\r
\r
    // The following fields should fit into one 32B slot, 20 + 2 + 1 + 1 + 4 + 4 = 32\r
    address public router; // null for any, 20B\r
    uint16 public tradingFeeBips; // 2B\r
    uint16 public routerShare; // Share of the trading fee that goes to the router in bips\r
    uint16 public licenseShare; // Share of the trading fee that goes to the router in bips\r
    uint24 public openFrom; // Market opening time\r
    uint24 public openTo; // Market closing time\r
\r
    constructor(address owner, address reactor, address currency, address router_) Ownable(owner) {\r
        REACTOR = reactor;\r
        CURRENCY = currency;\r
        licenseShare = 5000; // default license fee is 50% of fees\r
        router = router_;\r
        routerShare = 0;\r
        openFrom = 0;\r
        openTo = type(uint24).max;\r
    }\r
\r
    //// ADMINISTRATION ////\r
\r
    function isOpen() public view returns (bool) {\r
        return block.timestamp >= openFrom && block.timestamp < openTo;\r
    }\r
    \r
    /**\r
     * Opens the market.\r
     */\r
    function open() onlyOwner external {\r
        setTradingWindow(0, type(uint24).max);\r
    }\r
\r
    /**\r
     * Closes the market.\r
     */\r
    function close() onlyOwner external {\r
        setTradingWindow(0, 0);\r
    }\r
\r
    /**\r
     * Opens the market for a limited amount of time.\r
     * @param openTime The time in seconds since 1970-01-01 the market opens.\r
     * @param window The dureation in seconds for which the market stays open.\r
     */\r
    function setTradingWindow(uint24 openTime, uint24 window) onlyOwner public {\r
        openFrom = openTime;\r
        openTo = openTime + window;\r
    }\r
\r
    /**\r
     * Configures the permissible router or the null address for any.\r
     * \r
     * Having a trusted router helps with the prevention of front-running attacks as no\r
     * one else can front the router with a different matching of the submitted orders.\r
     */\r
    function setRouter(address router_, uint16 routerShare_) onlyOwner external {\r
        if (uint256(routerShare_) + licenseShare > ALL) revert InvalidConfiguration();\r
        router = router_;\r
        routerShare = routerShare_;\r
    }\r
\r
    /**\r
     * Configures the software license fee as agreed with the copyright owners.\r
     */\r
    function setLicenseFee(uint16 licenseShare_) onlyOwner external {\r
        if (uint256(licenseShare_) + routerShare > ALL) revert InvalidConfiguration();\r
        licenseShare = licenseShare_;\r
    }\r
\r
    function setTradingFee(uint16 tradingFeeBips_) onlyOwner external {\r
        if (tradingFeeBips_ > 500) revert InvalidConfiguration(); // commit to never set it above 5%\r
        tradingFeeBips = tradingFeeBips_;\r
    }\r
\r
    //// TRADING ////\r
\r
    /**\r
     * Create an order intent that can be signed by the owner.\r
     */\r
    function createBuyOrder(address owner, uint160 amountOut, address tokenIn, uint160 amountIn) public view returns (Intent memory) {\r
        return Intent(owner, address(this), CURRENCY, amountOut, tokenIn, amountIn, uint48(block.timestamp + 90 days), ISignatureTransfer(REACTOR).findFreeNonce(owner), new bytes(0));\r
    }\r
\r
    /**\r
     * Create an order intent that can be signed by the owner.\r
     * The tokenIn amount is reduced by the trading fee, which is always charged to the seller.\r
     */\r
    function createSellOrder(address owner, address tokenOut, uint160 amountOut, uint160 amountIn) public view returns (Intent memory) {\r
        return Intent(owner, address(this), tokenOut, amountOut, CURRENCY, amountIn * (10000 - tradingFeeBips) / 10000, uint48(block.timestamp + 90 days), ISignatureTransfer(REACTOR).findFreeNonce(owner), new bytes(0));\r
    }\r
\r
    /**\r
     * Calculate the hash to be signed.\r
     */\r
    function calculateHash(Intent calldata intent) public view returns (bytes32) {\r
        return IReactor(REACTOR).calculateHash(intent);\r
    }\r
\r
    /**\r
     * Stores an order in the Ethereum blockchain as a publicly readable event, so any allowed router\r
     * can pick it up and execute it against another valid order.\r
     * \r
     * In case the owner configured a specific router to be used, it is usually better to send the\r
     * order to the configured router directly through a suitable API. Note that all partially filled\r
     * orders and all filled orders are publicly recorded on-chain anyway, so taking the direct\r
     * transmission shortcut does not effectively preserve privacy.\r
     * \r
     * To invalidate an order, the owner must call the invalidateNonce function on the SignatureTransfer\r
     * contract found in this.ROUTER().TRANSFER().\r
     */\r
    function placeOrder(Intent calldata intent, bytes calldata signature) external {\r
        verifySignature(intent, signature);\r
        IReactor(REACTOR).signalIntent(intent, signature);\r
    }\r
\r
    /**\r
     * Verify the signature of an order.\r
     */\r
    function verifySignature(Intent calldata intent, bytes calldata sig) public view {\r
        if (intent.filler != address(this)) revert WrongFiller();\r
        IReactor(REACTOR).verify(intent, sig);\r
    }\r
\r
    /**\r
     * Check if an order can be executed and if yes, returns the maximum amount of the tokenOut.\r
     */\r
    function validateOrder(Intent calldata intent, bytes calldata sig) external view returns (uint256) {\r
        verifySignature(intent, sig);\r
        uint256 balance = IERC20(intent.tokenOut).balanceOf(intent.owner);\r
        if (balance == 0) revert NoBalance(intent.tokenOut, intent.owner);\r
        uint256 allowance = IERC20(intent.tokenOut).allowance(intent.owner, REACTOR);\r
        if (allowance == 0) revert NoAllowance(intent.tokenOut, intent.owner, REACTOR);\r
        uint256 permitted = ISignatureTransfer(REACTOR).getPermittedAmount(intent.owner, intent.amountOut, intent.nonce);\r
        if (permitted == 0) revert NonceUsed(intent.owner, intent.nonce);\r
        return permitted;\r
    }\r
\r
    /**\r
     * Validates a match between a seller and a buyer intent and returns the maximum amount of tokens that can be traded.\r
     */\r
    function validateMatch(Intent calldata sellerIntent, Intent calldata buyerIntent) external view returns (uint256) {\r
        return IReactor(REACTOR).getMaxValidAmount(sellerIntent, buyerIntent, tradingFeeBips);\r
    }\r
\r
    function process(Intent calldata seller, bytes calldata sellerSig, Intent calldata buyer, bytes calldata buyerSig, uint256 tradedTokens, bool buyerIsTaker) external {\r
        if (!isOpen()) revert MarketClosed(openFrom, openTo, block.timestamp);\r
        if (router != address(0) && msg.sender != router) revert WrongRouter(msg.sender, router);\r
        (uint256 proceeds, uint256 spread) = IReactor(REACTOR).process(seller, sellerSig, buyer, buyerSig, tradedTokens);\r
        uint256 price = buyerIsTaker ? proceeds * 10000 / (10000 - tradingFeeBips) : proceeds + spread;\r
        splitSpread(buyer.owner, seller, tradedTokens, price, buyerIsTaker ? buyer.owner : seller.owner, spread);\r
    }\r
\r
    function splitSpread(address buyer, Intent calldata seller, uint256 tradedTokens, uint256 price, address spreadRecipient, uint256 spread) internal {\r
        uint256 fees = tradingFeeBips * price / 10000;\r
        emit Trade(seller.owner, buyer, seller.tokenOut, tradedTokens, seller.tokenIn, price, fees);\r
        if (spread > fees) {\r
            IERC20(seller.tokenIn).transfer(spreadRecipient, spread - fees); // return excess spread to the taker who entered the order later\r
        }\r
    }\r
\r
    /**\r
     * Withdraw the accumulated fees applying a 50/50 split between the two addresses.\r
     * \r
     * The assumption is that this can be used to collect accumulated trading fees and to pay license fees\r
     * to Aktionariat in the same transaction for convenience.\r
     */\r
    function withdrawFees() external {\r
        withdrawFees(CURRENCY, IERC20(CURRENCY).balanceOf(address(this)));\r
    }\r
\r
    function withdrawFees(address currency, uint256 amount) public onlyOwner {\r
        uint256 split = amount * licenseShare / 10000;\r
        IERC20(currency).transfer(owner, amount - split); // rounded up\r
        IERC20(currency).transfer(LICENSE_FEE_RECIPIENT, split); // rounded down\r
        emit LicenseFeePaid(currency, LICENSE_FEE_RECIPIENT, split);\r
    }\r
\r
}"
    },
    "market/IntentHash.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import {IERC20} from "../ERC20/IERC20.sol";\r
import {PermitHash} from "./PermitHash.sol";\r
\r
struct Intent {\r
	address owner;\r
	address filler;\r
	address tokenOut; // The ERC20 token sent out\r
	uint160 amountOut; // The maximum amount\r
	address tokenIn; // The ERC20 token received\r
	uint160 amountIn; // The amount received in exchange for the maximum of the sent token\r
	uint48 expiration; // timestamp at which the intent expires\r
	uint48 nonce; // a unique value indexed per owner,token,and spender for each signature\r
	bytes data;\r
}\r
\r
/// @notice helpers for handling dutch order objects\r
library IntentHash {\r
\r
	bytes internal constant INTENT_TYPE =\r
		abi.encodePacked(\r
			"Intent(",\r
			"address owner,",\r
			"address filler,",\r
			"address tokenOut,",\r
			"uint160 amountOut,",\r
			"address tokenIn,",\r
			"uint160 amountIn,",\r
			"uint48 expiration,",\r
			"uint48 nonce,",\r
			"bytes data)"\r
		);\r
\r
	bytes32 internal constant INTENT_TYPE_HASH = keccak256(INTENT_TYPE);\r
\r
	string internal constant PERMIT2_INTENT_TYPE =\r
        string(abi.encodePacked("Intent witness)", INTENT_TYPE, PermitHash._TOKEN_PERMISSIONS_TYPESTRING));\r
\r
	/// @notice hash the given intent\r
	/// @param intent the intent to hash\r
	/// @return the eip-712 intent hash\r
	function hash(Intent calldata intent) internal pure returns (bytes32) {\r
		return\r
			keccak256(\r
				abi.encode(\r
					INTENT_TYPE_HASH,\r
					intent.owner,\r
					intent.filler,\r
					intent.tokenOut,\r
					intent.amountOut,\r
					intent.tokenIn,\r
					intent.amountIn,\r
					intent.expiration,\r
					intent.nonce,\r
					keccak256(intent.data)\r
				)\r
			);\r
	}\r
}\r
"
    },
    "market/ISignatureTransfer.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/// @title SignatureTransfer\r
/// @notice Handles ERC20 token transfers through signature based actions\r
/// @dev Requires user's token approval on the Permit2 contract\r
interface ISignatureTransfer {\r
    /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount\r
    /// @param maxAmount The maximum amount a spender can request to transfer\r
    error InvalidAmount(uint256 maxAmount);\r
\r
    /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred\r
    /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred\r
    error LengthMismatch();\r
\r
    /// @notice Emits an event when the owner successfully invalidates an unordered nonce.\r
    event UnorderedNonceInvalidation(address indexed owner, uint256 word, uint256 mask);\r
\r
    /// @notice The token and amount details for a transfer signed in the permit transfer signature\r
    struct TokenPermissions {\r
        // ERC20 token address\r
        address token;\r
        // the maximum amount that can be spent\r
        uint256 amount;\r
    }\r
\r
    /// @notice The signed permit message for a single token transfer\r
    struct PermitTransferFrom {\r
        TokenPermissions permitted;\r
        // a unique value for every token owner's signature to prevent signature replays\r
        uint256 nonce;\r
        // deadline on the permit signature\r
        uint256 deadline;\r
    }\r
\r
    /// @notice Specifies the recipient address and amount for batched transfers.\r
    /// @dev Recipients and amounts correspond to the index of the signed token permissions array.\r
    /// @dev Reverts if the requested amount is greater than the permitted signed amount.\r
    struct SignatureTransferDetails {\r
        // recipient address\r
        address to;\r
        // spender requested amount\r
        uint256 requestedAmount;\r
    }\r
\r
    /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection\r
    /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order\r
    /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce\r
    /// @dev It returns a uint256 bitmap\r
    /// @dev The index, or wordPosition is capped at type(uint248).max\r
    function nonceBitmap(address, uint256) external view returns (uint256);\r
\r
    /// @notice Transfers a token using a signed permit message\r
    /// @dev Reverts if the requested amount is greater than the permitted signed amount\r
    /// @param permit The permit data signed over by the owner\r
    /// @param owner The owner of the tokens to transfer\r
    /// @param transferDetails The spender's requested transfer details for the permitted token\r
    /// @param signature The signature to verify\r
    function permitTransferFrom(\r
        PermitTransferFrom memory permit,\r
        SignatureTransferDetails calldata transferDetails,\r
        address owner,\r
        bytes calldata signature\r
    ) external;\r
\r
    /// @notice Transfers a token using a signed permit message\r
    /// @notice Includes extra data provided by the caller to verify signature over\r
    /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition\r
    /// @dev Reverts if the requested amount is greater than the permitted signed amount\r
    /// @param permit The permit data signed over by the owner\r
    /// @param owner The owner of the tokens to transfer\r
    /// @param transferDetails The spender's requested transfer details for the permitted token\r
    /// @param witness Extra data to include when checking the user signature\r
    /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash\r
    /// @param signature The signature to verify\r
    function permitWitnessTransferFrom(\r
        PermitTransferFrom memory permit,\r
        SignatureTransferDetails calldata transferDetails,\r
        address owner,\r
        bytes32 witness,\r
        string calldata witnessTypeString,\r
        bytes calldata signature\r
    ) external;\r
\r
\r
    /// @notice Invalidates the bits specified in mask for the bitmap at the word position\r
    /// @dev The wordPos is maxed at type(uint248).max\r
    /// @param wordPos A number to index the nonceBitmap at\r
    /// @param mask A bitmap masked against msg.sender's current bitmap at the word position\r
    function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external;\r
\r
    function findFreeNonce(address owner) external view returns (uint48);\r
\r
    function getPermittedAmount(address owner, uint256 permitMax, uint48 nonce) external view returns (uint256);\r
}"
    },
    "market/IReactor.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
import {Intent} from "./IntentHash.sol";\r
\r
interface IReactor {\r
\r
    function calculateHash(Intent calldata intent) external view returns (bytes32);\r
    function verify(Intent calldata intent, bytes calldata sig) external view;\r
    function signalIntent(Intent calldata intent, bytes calldata signature) external;\r
    function getMaxValidAmount(Intent calldata sellerIntent, Intent calldata buyerIntent, uint16 minSpread) external view returns (uint256);\r
    function process(Intent calldata sellerIntent, bytes calldata sellerSig, Intent calldata buyerIntent, bytes calldata buyerSig, uint256 amount) external returns (uint256 proceeds, uint256 spread);\r
\r
}"
    },
    "utils/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
//\r
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol\r
//\r
// Modifications:\r
// - Replaced Context._msgSender() with msg.sender\r
// - Made leaner\r
// - Extracted interface\r
\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * @dev Contract module which provides a basic access control mechanism, where\r
 * there is an account (an owner) that can be granted exclusive access to\r
 * specific functions.\r
 *\r
 * By default, the owner account will be the one that deploys the contract. This\r
 * can later be changed with {transferOwnership}.\r
 *\r
 * This module is used through inheritance. It will make available the modifier\r
 * `onlyOwner`, which can be applied to your functions to restrict their use to\r
 * the owner.\r
 */\r
contract Ownable {\r
\r
    address public owner;\r
\r
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\r
\r
    error Ownable_NotOwner(address sender);\r
\r
    /**\r
     * @dev Initializes the contract setting the deployer as the initial owner.\r
     */\r
    constructor (address initialOwner) {\r
        owner = initialOwner;\r
        emit OwnershipTransferred(address(0), owner);\r
    }\r
\r
    modifier onlyOwner() {\r
        _checkOwner();\r
        _;\r
    }\r
\r
    /**\r
     * @dev Transfers ownership of the contract to a new account (`newOwner`).\r
     * Can only be called by the current owner.\r
     */\r
    function transferOwnership(address newOwner) external onlyOwner {\r
        emit OwnershipTransferred(owner, newOwner);\r
        owner = newOwner;\r
    }\r
\r
    function _checkOwner() internal view {\r
        if (msg.sender != owner) {\r
            revert Ownable_NotOwner(msg.sender);\r
        }\r
    }\r
}"
    },
    "ERC20/IERC20.sol": {
      "content": "/**\r
* SPDX-License-Identifier: MIT\r
*\r
* Copyright (c) 2016-2019 zOS Global Limited\r
*\r
*/\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include\r
 * the optional functions; to access them see `ERC20Detailed`.\r
 */\r
\r
interface IERC20 {\r
\r
    // Optional functions\r
    function name() external view returns (string memory);\r
\r
    function symbol() external view returns (string memory);\r
\r
    function decimals() external view returns (uint8);\r
\r
    /**\r
     * @dev Returns the amount of tokens in existence.\r
     */\r
    function totalSupply() external view returns (uint256);\r
\r
    /**\r
     * @dev Returns the amount of tokens owned by `account`.\r
     */\r
    function balanceOf(address account) external view returns (uint256);\r
\r
    /**\r
     * @dev Moves `amount` tokens from the caller's account to `recipient`.\r
     *\r
     * Returns a boolean value indicating whether the operation succeeded.\r
     *\r
     * Emits a `Transfer` event.\r
     */\r
    function transfer(address recipient, uint256 amount) external returns (bool);\r
\r
    /**\r
     * @dev Returns the remaining number of tokens that `spender` will be\r
     * allowed to spend on behalf of `owner` through `transferFrom`. This is\r
     * zero by default.\r
     *\r
     * This value changes when `approve` or `transferFrom` are called.\r
     */\r
    function allowance(address owner, address spender) external view returns (uint256);\r
\r
    /**\r
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\r
     *\r
     * Returns a boolean value indicating whether the operation succeeded.\r
     *\r
     * > Beware that changing an allowance with this method brings the risk\r
     * that someone may use both the old and the new allowance by unfortunate\r
     * transaction ordering. One possible solution to mitigate this race\r
     * condition is to first reduce the spender's allowance to 0 and set the\r
     * desired value afterwards:\r
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\r
     *\r
     * Emits an `Approval` event.\r
     */\r
    function approve(address spender, uint256 amount) external returns (bool);\r
\r
    /**\r
     * @dev Moves `amount` tokens from `sender` to `recipient` using the\r
     * allowance mechanism. `amount` is then deducted from the caller's\r
     * allowance.\r
     *\r
     * Returns a boolean value indicating whether the operation succeeded.\r
     *\r
     * Emits a `Transfer` event.\r
     */\r
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\r
\r
    /**\r
     * @dev Emitted when `value` tokens are moved from one account (`from`) to\r
     * another (`to`).\r
     *\r
     * Note that `value` may be zero.\r
     */\r
    event Transfer(address indexed from, address indexed to, uint256 value);\r
\r
    /**\r
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by\r
     * a call to `approve`. `value` is the new allowance.\r
     */\r
    event Approval(address indexed owner, address indexed spender, uint256 value);\r
\r
}"
    },
    "market/EIP712.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * Copied from github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol and modified.\r
 */\r
\r
/// @notice EIP712 helpers for permit2\r
/// @dev Maintains cross-chain replay protection in the event of a fork\r
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol\r
contract EIP712 {\r
    // Cache the domain separator as an immutable value, but also store the chain id that it\r
    // corresponds to, in order to invalidate the cached domain separator if the chain id changes.\r
    bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;\r
    uint256 private immutable _CACHED_CHAIN_ID;\r
\r
    bytes32 private constant _HASHED_NAME = keccak256("Permit2"); // TODO: what name should we use here?\r
    bytes32 private constant _TYPE_HASH =\r
        keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");\r
\r
    constructor() {\r
        _CACHED_CHAIN_ID = block.chainid;\r
        _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);\r
    }\r
\r
    /// @notice Returns the domain separator for the current chain.\r
    /// @dev Uses cached version if chainid and address are unchanged from construction.\r
    function DOMAIN_SEPARATOR() public view returns (bytes32) {\r
        return block.chainid == _CACHED_CHAIN_ID\r
            ? _CACHED_DOMAIN_SEPARATOR\r
            : _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);\r
    }\r
\r
    /// @notice Builds a domain separator using the current chainId and contract address.\r
    function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash) private view returns (bytes32) {\r
        return keccak256(abi.encode(typeHash, nameHash, block.chainid, address(this)));\r
    }\r
\r
    /// @notice Creates an EIP-712 typed data hash\r
    function _hashTypedData(bytes32 dataHash) internal view returns (bytes32) {\r
        return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash));\r
    }\r
}"
    },
    "market/PermitHash.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity >=0.8.0 <0.9.0;\r
\r
/**\r
 * Copied from github.com/Uniswap/permit2/blob/main/src/SignatureTransfer.sol and modified.\r
 */\r
import {ISignatureTransfer} from "./ISignatureTransfer.sol";\r
\r
library PermitHash {\r
    bytes32 public constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)");\r
\r
    bytes32 public constant _PERMIT_TRANSFER_FROM_TYPEHASH = keccak256(\r
        "PermitTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline)TokenPermissions(address token,uint256 amount)"\r
    );\r
\r
    string public constant _TOKEN_PERMISSIONS_TYPESTRING = "TokenPermissions(address token,uint256 amount)";\r
\r
    string public constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB =\r
        "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,";\r
\r
    function hash(ISignatureTransfer.PermitTransferFrom memory permit) internal view returns (bytes32) {\r
        bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted);\r
        return keccak256(\r
            abi.encode(_PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline)\r
        );\r
    }\r
\r
    function hashWithWitness(\r
        ISignatureTransfer.PermitTransferFrom memory permit,\r
        bytes32 witness,\r
        string memory witnessTypeString\r
    ) internal view returns (bytes32) {\r
        bytes32 typeHash = keccak256(abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, witnessTypeString));\r
\r
        bytes32 tokenPermissionsHash = _hashTokenPermissions(permit.permitted);\r
        return keccak256(abi.encode(typeHash, tokenPermissionsHash, msg.sender, permit.nonce, permit.deadline, witness));\r
    }\r
\r
    function _hashTokenPermissions(ISignatureTransfer.TokenPermissions memory permitted)\r
        private\r
        pure\r
        returns (bytes32)\r
    {\r
        return keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permitted));\r
    }\r
}"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": [],
    "evmVersion": "prague"
  }
}}

Tags:
ERC20, Multisig, Multi-Signature, Factory|addr:0x14e68a822d3313f6b2e4eb71ff18aef3b4479c04|verified:true|block:23433789|tx:0xcb2dd52ee73975f1310455323232a171471cf3910f31f2c67cf12fc4433bd315|first_check:1758737959

Submitted on: 2025-09-24 20:19:20

Comments

Log in to comment.

No comments yet.