账户
0xd3...1c66
0xD3...1c66

0xD3...1c66

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.19+commit.7dd6d404
语言
Solidity
合同源代码
文件 1 的 1:SwapCreator.sol
// SPDX-License-Identifier: LGPLv3
pragma solidity ^0.8.19;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }
}

/**
 * @dev Context variant with ERC2771 support.
 */
abstract contract ERC2771Context is Context {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    // TODO: this was modified to be public (is that ok?)
    address public immutable _trustedForwarder;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor(address trustedForwarder) {
        _trustedForwarder = trustedForwarder;
    }

    function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
        return forwarder == _trustedForwarder;
    }

    function _msgSender() internal view virtual override returns (address sender) {
        if (isTrustedForwarder(msg.sender)) {
            // The assembly code is more direct than the Solidity version using `abi.decode`.
            /// @solidity memory-safe-assembly
            assembly {
                sender := shr(96, calldataload(sub(calldatasize(), 20)))
            }
        } else {
            return super._msgSender();
        }
    }
}


/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @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);

    /**
     * @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 `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, 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 `from` to `to` 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 from, address to, uint256 amount) external returns (bool);
}


contract Secp256k1 {
    // solhint-disable-next-line
    uint256 private constant gx =
        0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798;
    // solhint-disable-next-line
    uint256 private constant m = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;

    // mulVerify returns true if `Q = s * G` on the secp256k1 curve
    // qKeccak is defined as uint256(keccak256(abi.encodePacked(qx, qy))
    function mulVerify(uint256 scalar, uint256 qKeccak) public pure returns (bool) {
        address qRes = ecrecover(0, 27, bytes32(gx), bytes32(mulmod(scalar, gx, m)));
        return uint160(qKeccak) == uint160(qRes);
    }
}

contract SwapCreator is ERC2771Context, Secp256k1 {
    // Swap state is PENDING when the swap is first created and funded
    // Alice sets Stage to READY when she sees the funds locked on the other chain.
    // this prevents Bob from withdrawing funds without locking funds on the other chain first
    // Stage is set to COMPLETED upon the swap value being claimed or refunded.
    enum Stage {
        INVALID,
        PENDING,
        READY,
        COMPLETED
    }

    struct Swap {
        // the swap initiator, Alice
        // address allowed to refund the ether for this swap
        address payable owner;
        // address allowed to claim the ether for this swap, Bob
        address payable claimer;
        // the keccak256 hash of the expected public key derived from the secret `s_b`.
        // this public key is a point on the secp256k1 curve
        bytes32 pubKeyClaim;
        // the keccak256 hash of the expected public key derived from the secret `s_a`.
        // this public key is a point on the secp256k1 curve
        bytes32 pubKeyRefund;
        // timestamp before which Alice can call either `setReady` or `refund`
        uint256 timeout0;
        // timestamp after which Bob cannot claim, only Alice can refund
        uint256 timeout1;
        // the asset being swapped: equal to address(0) for ETH, or an ERC-20 token address
        address asset;
        // the value of this swap
        uint256 value;
        // choose random
        uint256 nonce;
    }

    mapping(bytes32 => Stage) public swaps;

    event New(
        bytes32 swapID,
        bytes32 claimKey,
        bytes32 refundKey,
        uint256 timeout0,
        uint256 timeout1,
        address asset,
        uint256 value
    );
    event Ready(bytes32 indexed swapID);
    event Claimed(bytes32 indexed swapID, bytes32 indexed s);
    event Refunded(bytes32 indexed swapID, bytes32 indexed s);

    // returned when trying to initiate a swap with a zero value
    error ZeroValue();

    // returned when the pubKeyClaim or pubKeyRefund parameters for `newSwap` are zero
    error InvalidSwapKey();

    // returned when the claimer parameter for `newSwap` is the zero address
    error InvalidClaimer();

    // returned when the timeout0 or timeout1 parameters for `newSwap` are zero
    error InvalidTimeout();

    // returned when the ether sent with a `newSwap` transaction does not match the value parameter
    error InvalidValue();

    // returned when trying to initiate a swap with an ID that already exists
    error SwapAlreadyExists();

    // returned when trying to call `setReady` on a swap that is not in the PENDING stage
    error SwapNotPending();

    // returned when the caller of `setReady` or `refund` is not the swap owner
    error OnlySwapOwner();

    // returned when `claimRelayer` is not called by the trusted forwarder
    error OnlyTrustedForwarder();

    // returned when the signer of the relayed transaction is not the swap's claimer
    error OnlySwapClaimer();

    // returned when trying to call `claim` or `refund` on an invalid swap
    error InvalidSwap();

    // returned when trying to call `claim` or `refund` on a swap that's already completed
    error SwapCompleted();

    // returned when trying to call `claim` on a swap that's not set to ready or the first timeout has not been reached
    error TooEarlyToClaim();

    // returned when trying to call `claim` on a swap where the second timeout has been reached
    error TooLateToClaim();

    // returned when it's the counterparty's turn to claim and refunding is not allowed
    error NotTimeToRefund();

    // returned when the provided secret does not match the expected public key
    error InvalidSecret();

    constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {} // solhint-disable-line

    // newSwap creates a new Swap instance with the given parameters.
    // it returns the swap's ID.
    // _timeoutDuration0: duration between the current timestamp and timeout0
    // _timeoutDuration1: duration between timeout0 and timeout1
    function newSwap(
        bytes32 _pubKeyClaim,
        bytes32 _pubKeyRefund,
        address payable _claimer,
        uint256 _timeoutDuration0,
        uint256 _timeoutDuration1,
        address _asset,
        uint256 _value,
        uint256 _nonce
    ) public payable returns (bytes32) {
        if (_value == 0) revert ZeroValue();
        if (_asset == address(0)) {
            if (_value != msg.value) revert InvalidValue();
        } else {
            // transfer ERC-20 token into this contract
            // WARN: fee-on-transfer tokens are not supported
            IERC20(_asset).transferFrom(msg.sender, address(this), _value);
        }

        if (_pubKeyClaim == 0 || _pubKeyRefund == 0) revert InvalidSwapKey();
        if (_claimer == address(0)) revert InvalidClaimer();
        if (_timeoutDuration0 == 0 || _timeoutDuration1 == 0) revert InvalidTimeout();

        Swap memory swap = Swap({
            owner: payable(msg.sender),
            pubKeyClaim: _pubKeyClaim,
            pubKeyRefund: _pubKeyRefund,
            claimer: _claimer,
            timeout0: block.timestamp + _timeoutDuration0,
            timeout1: block.timestamp + _timeoutDuration0 + _timeoutDuration1,
            asset: _asset,
            value: _value,
            nonce: _nonce
        });

        bytes32 swapID = keccak256(abi.encode(swap));

        // make sure this isn't overriding an existing swap
        if (swaps[swapID] != Stage.INVALID) revert SwapAlreadyExists();

        emit New(
            swapID,
            _pubKeyClaim,
            _pubKeyRefund,
            swap.timeout0,
            swap.timeout1,
            swap.asset,
            swap.value
        );
        swaps[swapID] = Stage.PENDING;
        return swapID;
    }

    // Alice should call setReady() before timeout0 once she verifies the XMR has been locked
    function setReady(Swap memory _swap) public {
        bytes32 swapID = keccak256(abi.encode(_swap));
        if (swaps[swapID] != Stage.PENDING) revert SwapNotPending();
        if (_swap.owner != msg.sender) revert OnlySwapOwner();
        swaps[swapID] = Stage.READY;
        emit Ready(swapID);
    }

    // Bob can claim if:
    // - (Alice has set the swap to `ready` or it's past timeout0) and it's before timeout1
    function claim(Swap memory _swap, bytes32 _s) public {
        _claim(_swap, _s);

        // send ether to swap claimer
        if (_swap.asset == address(0)) {
            _swap.claimer.transfer(_swap.value);
        } else {
            // WARN: this will FAIL for fee-on-transfer or rebasing tokens if the token
            // transfer reverts (i.e. if this contract does not contain _swap.value tokens),
            // exposing Bob's secret while giving him nothing.
            IERC20(_swap.asset).transfer(_swap.claimer, _swap.value);
        }
    }

    // Bob can claim if:
    // - (Alice has set the swap to `ready` or it's past timeout0) and it's before timeout1
    // This function is only callable by the trusted forwarder.
    // It transfers the fee to the originator of the transaction.
    function claimRelayer(Swap memory _swap, bytes32 _s, uint256 fee) public {
        if (!isTrustedForwarder(msg.sender)) revert OnlyTrustedForwarder();
        _claim(_swap, _s);

        // send ether to swap claimer, subtracting the relayer fee
        // which is sent to the originator of the transaction.
        // tx.origin is okay here, since it isn't for authentication purposes.
        if (_swap.asset == address(0)) {
            _swap.claimer.transfer(_swap.value - fee);
            payable(tx.origin).transfer(fee); // solhint-disable-line
        } else {
            // WARN: this will FAIL for fee-on-transfer or rebasing tokens if the token
            // transfer reverts (i.e. if this contract does not contain _swap.value tokens),
            // exposing Bob's secret while giving him nothing.
            IERC20(_swap.asset).transfer(_swap.claimer, _swap.value - fee);
            IERC20(_swap.asset).transfer(tx.origin, fee); // solhint-disable-line
        }
    }

    function _claim(Swap memory _swap, bytes32 _s) internal {
        bytes32 swapID = keccak256(abi.encode(_swap));
        Stage swapStage = swaps[swapID];
        if (swapStage == Stage.INVALID) revert InvalidSwap();
        if (swapStage == Stage.COMPLETED) revert SwapCompleted();
        if (_msgSender() != _swap.claimer) revert OnlySwapClaimer();
        if (block.timestamp < _swap.timeout0 && swapStage != Stage.READY) revert TooEarlyToClaim();
        if (block.timestamp >= _swap.timeout1) revert TooLateToClaim();

        verifySecret(_s, _swap.pubKeyClaim);
        emit Claimed(swapID, _s);
        swaps[swapID] = Stage.COMPLETED;
    }

    // Alice can claim a refund:
    // - Until timeout0 unless she calls setReady
    // - After timeout1
    function refund(Swap memory _swap, bytes32 _s) public {
        bytes32 swapID = keccak256(abi.encode(_swap));
        Stage swapStage = swaps[swapID];
        if (swapStage == Stage.INVALID) revert InvalidSwap();
        if (swapStage == Stage.COMPLETED) revert SwapCompleted();
        if (_swap.owner != msg.sender) revert OnlySwapOwner();
        if (
            block.timestamp < _swap.timeout1 &&
            (block.timestamp > _swap.timeout0 || swapStage == Stage.READY)
        ) revert NotTimeToRefund();

        verifySecret(_s, _swap.pubKeyRefund);
        emit Refunded(swapID, _s);

        // send asset back to swap owner
        swaps[swapID] = Stage.COMPLETED;
        if (_swap.asset == address(0)) {
            _swap.owner.transfer(_swap.value);
        } else {
            IERC20(_swap.asset).transfer(_swap.owner, _swap.value);
        }
    }

    function verifySecret(bytes32 _s, bytes32 _hashedPubkey) internal pure {
        if (!mulVerify(uint256(_s), uint256(_hashedPubkey))) revert InvalidSecret();
    }
}
设置
{
  "compilationTarget": {
    "SwapCreator.sol": "SwapCreator"
  },
  "evmVersion": "paris",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"address","name":"trustedForwarder","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidClaimer","type":"error"},{"inputs":[],"name":"InvalidSecret","type":"error"},{"inputs":[],"name":"InvalidSwap","type":"error"},{"inputs":[],"name":"InvalidSwapKey","type":"error"},{"inputs":[],"name":"InvalidTimeout","type":"error"},{"inputs":[],"name":"InvalidValue","type":"error"},{"inputs":[],"name":"NotTimeToRefund","type":"error"},{"inputs":[],"name":"OnlySwapClaimer","type":"error"},{"inputs":[],"name":"OnlySwapOwner","type":"error"},{"inputs":[],"name":"OnlyTrustedForwarder","type":"error"},{"inputs":[],"name":"SwapAlreadyExists","type":"error"},{"inputs":[],"name":"SwapCompleted","type":"error"},{"inputs":[],"name":"SwapNotPending","type":"error"},{"inputs":[],"name":"TooEarlyToClaim","type":"error"},{"inputs":[],"name":"TooLateToClaim","type":"error"},{"inputs":[],"name":"ZeroValue","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"swapID","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"swapID","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"claimKey","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"refundKey","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"timeout0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timeout1","type":"uint256"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"New","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"swapID","type":"bytes32"}],"name":"Ready","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"swapID","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"Refunded","type":"event"},{"inputs":[],"name":"_trustedForwarder","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address payable","name":"owner","type":"address"},{"internalType":"address payable","name":"claimer","type":"address"},{"internalType":"bytes32","name":"pubKeyClaim","type":"bytes32"},{"internalType":"bytes32","name":"pubKeyRefund","type":"bytes32"},{"internalType":"uint256","name":"timeout0","type":"uint256"},{"internalType":"uint256","name":"timeout1","type":"uint256"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"}],"internalType":"struct SwapCreator.Swap","name":"_swap","type":"tuple"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address payable","name":"owner","type":"address"},{"internalType":"address payable","name":"claimer","type":"address"},{"internalType":"bytes32","name":"pubKeyClaim","type":"bytes32"},{"internalType":"bytes32","name":"pubKeyRefund","type":"bytes32"},{"internalType":"uint256","name":"timeout0","type":"uint256"},{"internalType":"uint256","name":"timeout1","type":"uint256"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"}],"internalType":"struct SwapCreator.Swap","name":"_swap","type":"tuple"},{"internalType":"bytes32","name":"_s","type":"bytes32"},{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"claimRelayer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"scalar","type":"uint256"},{"internalType":"uint256","name":"qKeccak","type":"uint256"}],"name":"mulVerify","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_pubKeyClaim","type":"bytes32"},{"internalType":"bytes32","name":"_pubKeyRefund","type":"bytes32"},{"internalType":"address payable","name":"_claimer","type":"address"},{"internalType":"uint256","name":"_timeoutDuration0","type":"uint256"},{"internalType":"uint256","name":"_timeoutDuration1","type":"uint256"},{"internalType":"address","name":"_asset","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_nonce","type":"uint256"}],"name":"newSwap","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address payable","name":"owner","type":"address"},{"internalType":"address payable","name":"claimer","type":"address"},{"internalType":"bytes32","name":"pubKeyClaim","type":"bytes32"},{"internalType":"bytes32","name":"pubKeyRefund","type":"bytes32"},{"internalType":"uint256","name":"timeout0","type":"uint256"},{"internalType":"uint256","name":"timeout1","type":"uint256"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"}],"internalType":"struct SwapCreator.Swap","name":"_swap","type":"tuple"},{"internalType":"bytes32","name":"_s","type":"bytes32"}],"name":"refund","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address payable","name":"owner","type":"address"},{"internalType":"address payable","name":"claimer","type":"address"},{"internalType":"bytes32","name":"pubKeyClaim","type":"bytes32"},{"internalType":"bytes32","name":"pubKeyRefund","type":"bytes32"},{"internalType":"uint256","name":"timeout0","type":"uint256"},{"internalType":"uint256","name":"timeout1","type":"uint256"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"}],"internalType":"struct SwapCreator.Swap","name":"_swap","type":"tuple"}],"name":"setReady","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"swaps","outputs":[{"internalType":"enum SwapCreator.Stage","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}]