[
  {
    "path": ".gitignore",
    "content": ".idea"
  },
  {
    "path": "LICENSE.md",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n   Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n   stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n   that You distribute, all copyright, patent, trademark, and\n   attribution notices from the Source form of the Work,\n   excluding those notices that do not pertain to any part of\n   the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n   distribution, then any Derivative Works that You distribute must\n   include a readable copy of the attribution notices contained\n   within such NOTICE file, excluding those notices that do not\n   pertain to any part of the Derivative Works, in at least one\n   of the following places: within a NOTICE text file distributed\n   as part of the Derivative Works; within the Source form or\n   documentation, if provided along with the Derivative Works; or,\n   within a display generated by the Derivative Works, if and\n   wherever such third-party notices normally appear. The contents\n   of the NOTICE file are for informational purposes only and\n   do not modify the License. You may add Your own attribution\n   notices within Derivative Works that You distribute, alongside\n   or as an addendum to the NOTICE text from the Work, provided\n   that such additional attribution notices cannot be construed\n   as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\n\nCopyright 2023 Coinbase, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License."
  },
  {
    "path": "README.md",
    "content": "# Coinbase Commerce Onchain Payment Protocol\n\nThe Coinbase Commerce Onchain Payment Protocol allows payers and merchants to transact using the blockchain as a settlement layer and source of truth.\nIt provides the following benefits over \"traditional\" cryptocurrency payments:\n\n- Guaranteed settlement: merchants always receive exactly the amount that they request.\n- Automatic conversion: payers can pay with any token that has liquidity on Uniswap, without exposing merchants to price volatility.\n- Removal of payment errors: it is no longer possible to pay the wrong amount or to the wrong address.\n\n### Contract Deployments\n\nAs of July 31, 2024, the Commerce Onchain Payment Protocol is deployed in the following locations:\n\n| Chain    | Environment     | Address                                      |\n| -------- | --------------- | -------------------------------------------- |\n| Ethereum | Mainnet         | `0x1DAe28D7007703196d6f456e810F67C33b51b25C` |\n| Ethereum | Sepolia Testnet | `0x96A08D8e8631b6dB52Ea0cbd7232d9A85d239147` |\n| Polygon  | Mainnet         | `0xc2252Ce3348B8dAf90583E53e07Be53d3aE728FB` |\n| Polygon  | Amoy Testnet    | `0x1A8f790a10D26bAd97dB8Da887D212eA49461cCC` |\n| Base     | Mainnet         | `0xeADE6bE02d043b3550bE19E960504dbA14A14971` |\n| Base     | Sepolia Testnet | `0x96A08D8e8631b6dB52Ea0cbd7232d9A85d239147` |\n\nSince the contract is non-upgradeable, these addresses will change when new\nversions are deployed.\n\n### Browsing this Repo\n\nThe core source code can be found in [Transfers.sol](contracts/transfers/Transfers.sol).\n\nExcluded from this repo is a copy of [Uniswap/permit2](https://github.com/Uniswap/permit2),\nwhich would be copied to `contracts/permit2` in order to compile.\n\n## Overview\n\n### Operators\n\nThe Transfers contract facilitates payments from a payer to a merchant. Before\nit may be used, an \"operator\" must register with the contract and specify\na destination for fees. This operator is responsible for setting merchants up\nwith the protocol and providing a UI for both merchants and payers to interact\nwith it. Registering as an operator is permissionless, and Coinbase maintains\ncontrol of an address used as the operator for Coinbase Commerce.\n\n### Transfer Intents\n\nOnce an operator is registered, they may begin facilitating payments. Individual\npayments use a primitive called a `TransferIntent`, represented by a Solidity\nstruct of the same name. This struct specifies the following:\n\n- The merchant's address\n- The currency the merchant wishes to receive\n- The amount of that currency the merchant wishes to receive\n- The deadline by which the payment must be made\n- The payer's address\n- The chain the payer will pay on\n- The address any refund should be directed to\n- The operator who is facilitating the payment\n- The fee the operator should receive\n- A unique identifier for identifying the payment\n- A signature (and optional signature prefix) from the operator\n\nAlong with these attributes, a `TransferIntent` must be signed by the operator.\nThis allows an operator to be selective about what payments to allow based on\ninternal policies, legal requirements, or other reasons. It also ensures that\na `TransferIntent` cannot be forged or have its data modified in any way.\n\n### Contract Guarantees\n\nThe contract ensures that, for a given valid `TransferIntent`:\n\n- The merchant always receives the exact amount requested\n- The merchant never receives payments past a stated deadline\n- The merchant never receives more than one payment\n- Payments may be made using the merchant's requested currency, or swapped from\n  another token as part of the payment transaction\n- Unsuccessful or partial payments will never reach the merchant, thus\n  guaranteeing that payments are atomic. Either the merchant is correctly paid\n  in full and the fee is correctly charged, or the transaction reverts and no\n  state is changed onchain.\n\n### Contract payment methods\n\nDepending on the settlement token and the input token, along with the way\nin which the payer allows movement of their input token, a frontend must select\nthe appropriate method by which to pay a `TransferIntent`. These methods are:\n\n- `transferNative`: The merchant wants ETH and the payer wants to pay ETH\n- `transferToken`: The merchant wants a token and the payer wants to pay with\n  that token. Uses Permit2 for token movement.\n- `transferTokenPreApproved`: Same as `transferToken`, except the Transfers\n  contract is directly approved by the payer for the payment token\n- `wrapAndTransfer`: The merchant wants WETH and the payer wants to pay ETH\n- `unwrapAndTransfer`: The merchant wants ETH and the payer wants to pay WETH\n- `unwrapAndTransferPreApproved`: Same as `unwrapAndTransfer`, except the\n  Transfers contract is directly approved by the payer for WETH\n- `swapAndTransferUniswapV3Native`: The merchant wants a token and the payer\n  wants to pay ETH. The token must have sufficient liquidity with ETH on Uniswap\n  V3.\n- `swapAndTransferUniswapV3Token`: The merchant wants either ETH or a token and\n  the payer wants to pay with a different token. The payment token must have\n  sufficient liquidity with the settlement token on Uniswap V3.\n- `swapAndTransferUniswapV3TokenPreApproved`: Same as\n  `swapAndTransferUniswapV3Token`, except the Transfers contract is directly\n  approved by the payer for the payment token\n\nFor any EVM-compatible network where ETH is not the native/gas currency, the\nabove descriptions should substitute that currency. For example, payments on\nPolygon would use MATIC in the above descriptions.\n\n### Payment Transaction Results\n\nWhen the payment is successful, a `Transferred` event is emitted by the contract\nwith details about:\n\n- The operator address\n- The unique id of the `TransferIntent`\n- The merchant (recipient) address\n- The payer (sender) address\n- The input token that was spent by the payer\n- The amount of the input token spent by the payer\n\nIn the case of errors, a specific error type is returned with details about what\nwent wrong.\n"
  },
  {
    "path": "contracts/interfaces/IERC7597.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts/interfaces/IERC2612.sol\";\n\ninterface IERC7597 is IERC2612 {\n    function permit(\n        address owner,\n        address spender,\n        uint256 value,\n        uint256 deadline,\n        bytes memory signature\n    ) external;\n}\n"
  },
  {
    "path": "contracts/interfaces/ITransfers.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.17;\n\nimport \"../permit2/src/interfaces/ISignatureTransfer.sol\";\n\n// @notice Description of the transfer\n// @member recipientAmount Amount of currency to transfer\n// @member deadline The timestamp by when the transfer must be in a block.\n// @member chainId The chain which the transfer must occur on.\n// @member recipient The address which will receive the funds.\n// @member recipientCurrency The currency address that amount is priced in.\n// @member refundDestination The address which will receive any refunds. If blank, this will be msg.sender.\n// @member feeAmount The fee value (in currency) to send to the operator.\n// @member id An ID which can be used to track payments.\n// @member operator The address of the operator (who created and signed the intent).\n// @member signature A hash of all the other struct properties signed by the operator.\n// @member prefix An alternate signature prefix to use instead of the standard EIP-191 \"\\x19Ethereum Signed Message:\\n\"\n// @dev signature=keccak256(encodePacked(...allPropsInOrderExceptSignatureAndPrefix, chainId, _msgSender(), address(transfersContract))\nstruct TransferIntent {\n    uint256 recipientAmount;\n    uint256 deadline;\n    address payable recipient;\n    address recipientCurrency;\n    address refundDestination;\n    uint256 feeAmount;\n    bytes16 id;\n    address operator;\n    bytes signature;\n    bytes prefix;\n}\n\nstruct Permit2SignatureTransferData {\n    ISignatureTransfer.PermitTransferFrom permit;\n    ISignatureTransfer.SignatureTransferDetails transferDetails;\n    bytes signature;\n}\n\nstruct EIP2612SignatureTransferData {\n    address owner; // The owner of the funds\n    bytes signature; // The signature for the permit\n}\n\n// @title Transfers Contract\n// @notice Functions for making checked transfers between accounts\ninterface ITransfers {\n    // @notice Emitted when a transfer is completed\n    // @param operator The operator for the transfer intent\n    // @param id The ID of the transfer intent\n    // @param recipient Who recieved the funds.\n    // @param sender Who sent the funds.\n    // @param spentAmount How much the payer sent\n    // @param spentCurrency What currency the payer sent\n    event Transferred(\n        address indexed operator,\n        bytes16 id,\n        address recipient,\n        address sender,\n        uint256 spentAmount,\n        address spentCurrency\n    );\n\n    // @notice Raised when a native currency transfer fails\n    // @param recipient Who the transfer was intended for\n    // @param amount The amount of the transfer\n    // @param isRefund Whether the transfer was part of a refund\n    // @param data The data returned from the failed call\n    error NativeTransferFailed(address recipient, uint256 amount, bool isRefund, bytes data);\n\n    // @notice Emitted when an operator is registered\n    // @param operator The operator that was registered\n    // @param feeDestination The new fee destination for the operator\n    event OperatorRegistered(address operator, address feeDestination);\n\n    // @notice Emitted when an operator is unregistered\n    // @param operator The operator that was registered\n    event OperatorUnregistered(address operator);\n\n    // @notice Raised when the operator in the intent is not registered\n    error OperatorNotRegistered();\n\n    // @notice Raised when the intent signature is invalid\n    error InvalidSignature();\n\n    // @notice Raised when the invalid amount of native currency is provided\n    // @param difference The surplus (or deficit) amount sent\n    error InvalidNativeAmount(int256 difference);\n\n    // @notice Raised when the payer does not have enough of the payment token\n    // @param difference The balance deficit\n    error InsufficientBalance(uint256 difference);\n\n    // @notice Raised when the payer has not approved enough of the payment token\n    // @param difference The allowance deficit\n    error InsufficientAllowance(uint256 difference);\n\n    // @notice Raised when providing an intent with the incorrect currency. e.g. a USDC intent to `wrapAndTransfer`\n    // @param attemptedCurrency The currency the payer attempted to pay with\n    error IncorrectCurrency(address attemptedCurrency);\n\n    // @notice Raised when the permit2 transfer details are incorrect\n    error InvalidTransferDetails();\n\n    // @notice Raised when an intent is paid past its deadline\n    error ExpiredIntent();\n\n    // @notice Raised when an intent's recipient is the null address\n    error NullRecipient();\n\n    // @notice Raised when an intent has already been processed\n    error AlreadyProcessed();\n\n    // @notice Raised when a transfer does not result in the correct balance increase,\n    // such as with fee-on-transfer tokens\n    error InexactTransfer();\n\n    // @notice Raised when a swap fails and returns a reason string\n    // @param reason The error reason returned from the swap\n    error SwapFailedString(string reason);\n\n    // @notice Raised when a swap fails and returns another error\n    // @param reason The error reason returned from the swap\n    error SwapFailedBytes(bytes reason);\n\n    // @notice Send the exact amount of the native currency from the sender to the recipient.\n    // @dev The intent's recipient currency must be the native currency.\n    // @param _intent The intent which describes the transfer\n    function transferNative(TransferIntent calldata _intent) external payable;\n\n    // @notice Transfer the exact amount of any ERC-20 token from the sender to the recipient.\n    // @dev The intent's recipient currency must be an ERC-20 token matching the one in `_signatureTransferData`.\n    // @dev The user must have approved the Permit2 contract for at least `_intent.recipientAmount + _intent.feeAmount`\n    //      with the `_intent.recipientCurrency` ERC-20 contract prior to invoking.\n    // @param _intent The intent which describes the transfer\n    function transferToken(\n        TransferIntent calldata _intent,\n        Permit2SignatureTransferData calldata _signatureTransferData\n    ) external;\n\n    // @notice Transfer the exact amount of any ERC-20 token from the sender to the recipient.\n    // @dev The intent's recipient currency must be an ERC-20 token.\n    // @dev The user must have approved this contract for at least `_intent.recipientAmount + _intent.feeAmount`\n    //      with the `_intent.recipientCurrency` ERC-20 contract prior to invoking.\n    // @param _intent The intent which describes the transfer\n    function transferTokenPreApproved(TransferIntent calldata _intent) external;\n\n    // @notice Takes native currency (e.g. ETH) from the sender and sends wrapped currency (e.g. wETH) to the recipient.\n    // @dev The intent's recipient currency must be the wrapped native currency.\n    // @param _intent The intent which describes the transfer\n    function wrapAndTransfer(TransferIntent calldata _intent) external payable;\n\n    // @notice Takes wrapped currency (e.g. wETH) from the sender and sends native currency (e.g. ETH) to the recipient.\n    // @dev The intent's recipient currency must be the native currency.\n    // @dev The user must have approved the Permit2 contract for at least `_intent.recipientAmount + _intent.feeAmount`\n    //      with the wETH contract prior to invoking.\n    // @param _intent The intent which describes the transfer\n    // @param _signatureTransferData The signed Permit2 transfer data for the payment\n    function unwrapAndTransfer(\n        TransferIntent calldata _intent,\n        Permit2SignatureTransferData calldata _signatureTransferData\n    ) external;\n\n    // @notice Takes wrapped currency (e.g. wETH) from the sender and sends native currency (e.g. ETH) to the recipient.\n    // @dev The intent's recipient currency must be the native currency.\n    // @dev The user must have approved this contract for at least `_intent.recipientAmount + _intent.feeAmount` with the wETH contract prior to invoking.\n    // @param _intent The intent which describes the transfer\n    function unwrapAndTransferPreApproved(TransferIntent calldata _intent) external;\n\n    // @notice Allows the sender to pay for an intent with a swap from the native currency using Uniswap.\n    // @param _intent The intent which describes the transfer\n    // @param poolFeesTier The Uniswap pool fee the user wishes to pay. See: https://docs.uniswap.org/protocol/concepts/V3-overview/fees#pool-fees-tiers\n    function swapAndTransferUniswapV3Native(TransferIntent calldata _intent, uint24 poolFeesTier) external payable;\n\n    // @notice Allows the sender to pay for an intent with a swap from any ERC-20 token using Uniswap.\n    // @dev The user must have approved the Permit2 contract for at least `_signatureTransferData.transferDetails.requestedAmount`\n    //      with the `_signatureTransferData.permit.permitted.token` ERC-20 contract prior to invoking.\n    // @param _intent The intent which describes the transfer\n    // @param _signatureTransferData The signed Permit2 transfer data for the payment\n    // @param poolFeesTier The Uniswap pool fee the user wishes to pay. See: https://docs.uniswap.org/protocol/concepts/V3-overview/fees#pool-fees-tiers\n    function swapAndTransferUniswapV3Token(\n        TransferIntent calldata _intent,\n        Permit2SignatureTransferData calldata _signatureTransferData,\n        uint24 poolFeesTier\n    ) external;\n\n    // @notice Allows the sender to pay for an intent with a swap from any ERC-20 token using Uniswap.\n    // @dev The user must have approved this contract for at least `maxWillingToPay` with the `_tokenIn` ERC-20 contract prior to invoking.\n    // @param _intent The intent which describes the transfer\n    // @param _tokenIn The currency address which the sender wishes to pay for the intent.\n    // @param maxWillingToPay The maximum amount of _tokenIn the sender is willing to pay.\n    // @param poolFeesTier The Uniswap pool fee the user wishes to pay. See: https://docs.uniswap.org/protocol/concepts/V3-overview/fees#pool-fees-tiers\n    function swapAndTransferUniswapV3TokenPreApproved(\n        TransferIntent calldata _intent,\n        address _tokenIn,\n        uint256 maxWillingToPay,\n        uint24 poolFeesTier\n    ) external;\n\n    // @notice Allows the sender to pay for an intent with gasless transaction\n    // @param _intent The intent which describes the transfer\n    // @param _signatureTransferData The signed EIP-2612 permit data for the payment\n    function subsidizedTransferToken(\n        TransferIntent calldata _intent,\n        EIP2612SignatureTransferData calldata _signatureTransferData\n    ) external;\n}\n"
  },
  {
    "path": "contracts/interfaces/IWrappedNativeCurrency.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n// @title Represented wrapped (e.g. wETH) currencies\ninterface IWrappedNativeCurrency is IERC20 {\n    function deposit() external payable;\n\n    function withdraw(uint256) external;\n}\n"
  },
  {
    "path": "contracts/transfers/Transfers.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport \"@openzeppelin/contracts/security/Pausable.sol\";\nimport \"@openzeppelin/contracts/security/ReentrancyGuard.sol\";\nimport \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@openzeppelin/contracts/utils/Context.sol\";\nimport \"@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol\";\nimport {Commands as UniswapCommands} from \"@uniswap/universal-router/contracts/libraries/Commands.sol\";\nimport {Constants as UniswapConstants} from \"@uniswap/universal-router/contracts/libraries/Constants.sol\";\nimport \"../interfaces/IWrappedNativeCurrency.sol\";\nimport \"../interfaces/ITransfers.sol\";\nimport \"../interfaces/IERC7597.sol\";\nimport \"../utils/Sweepable.sol\";\nimport \"../permit2/src/Permit2.sol\";\n\n// Uniswap error selectors, used to surface information when swaps fail\n// Pulled from @uniswap/universal-router/out/V3SwapRouter.sol/V3SwapRouter.json after compiling with forge\nbytes32 constant V3_INVALID_SWAP = keccak256(hex\"316cf0eb\");\nbytes32 constant V3_TOO_LITTLE_RECEIVED = keccak256(hex\"39d35496\");\nbytes32 constant V3_TOO_MUCH_REQUESTED = keccak256(hex\"739dbe52\");\nbytes32 constant V3_INVALID_AMOUNT_OUT = keccak256(hex\"d4e0248e\");\nbytes32 constant V3_INVALID_CALLER = keccak256(hex\"32b13d91\");\n\n// @inheritdoc ITransfers\ncontract Transfers is Context, Ownable, Pausable, ReentrancyGuard, Sweepable, ITransfers {\n    using SafeERC20 for IERC20;\n    using SafeERC20 for IWrappedNativeCurrency;\n\n    // @dev Map of operator addresses and fee destinations.\n    mapping(address => address) private feeDestinations;\n\n    // @dev Map of operator addresses to a map of transfer intent ids that have been processed\n    mapping(address => mapping(bytes16 => bool)) private processedTransferIntents;\n\n    // @dev Represents native token of a chain (e.g. ETH or MATIC)\n    address private immutable NATIVE_CURRENCY = address(0);\n\n    // @dev Uniswap on-chain contract\n    IUniversalRouter private immutable uniswap;\n\n    // @dev permit2 SignatureTransfer contract address. Used for tranferring tokens with a signature instead of a full transaction.\n    // See: https://github.com/Uniswap/permit2\n    Permit2 public immutable permit2;\n\n    // @dev Canonical wrapped token for this chain. e.g. (wETH or wMATIC).\n    IWrappedNativeCurrency private immutable wrappedNativeCurrency;\n\n    // @param _uniswap The address of the Uniswap V3 swap router\n    // @param _wrappedNativeCurrency The address of the wrapped token for this chain\n    constructor(\n        IUniversalRouter _uniswap,\n        Permit2 _permit2,\n        address _initialOperator,\n        address _initialFeeDestination,\n        IWrappedNativeCurrency _wrappedNativeCurrency\n    ) {\n        require(\n            address(_uniswap) != address(0) &&\n                address(_permit2) != address(0) &&\n                address(_wrappedNativeCurrency) != address(0) &&\n                _initialOperator != address(0) &&\n                _initialFeeDestination != address(0),\n            \"invalid constructor parameters\"\n        );\n        uniswap = _uniswap;\n        permit2 = _permit2;\n        wrappedNativeCurrency = _wrappedNativeCurrency;\n\n        // Sets an initial operator to enable immediate payment processing\n        feeDestinations[_initialOperator] = _initialFeeDestination;\n    }\n\n    // @dev Raises errors if the intent is invalid\n    // @param _intent The intent to validate\n    modifier validIntent(TransferIntent calldata _intent, address sender) {\n        bytes32 hash = keccak256(\n            abi.encodePacked(\n                _intent.recipientAmount,\n                _intent.deadline,\n                _intent.recipient,\n                _intent.recipientCurrency,\n                _intent.refundDestination,\n                _intent.feeAmount,\n                _intent.id,\n                _intent.operator,\n                block.chainid,\n                sender,\n                address(this)\n            )\n        );\n\n        bytes32 signedMessageHash;\n        if (_intent.prefix.length == 0) {\n            // Use 'default' message prefix.\n            signedMessageHash = ECDSA.toEthSignedMessageHash(hash);\n        } else {\n            // Use custom message prefix.\n            signedMessageHash = keccak256(abi.encodePacked(_intent.prefix, hash));\n        }\n\n        address signer = ECDSA.recover(signedMessageHash, _intent.signature);\n\n        if (signer != _intent.operator) {\n            revert InvalidSignature();\n        }\n\n        if (_intent.deadline < block.timestamp) {\n            revert ExpiredIntent();\n        }\n\n        if (_intent.recipient == address(0)) {\n            revert NullRecipient();\n        }\n\n        if (processedTransferIntents[_intent.operator][_intent.id]) {\n            revert AlreadyProcessed();\n        }\n\n        _;\n    }\n\n    // @dev Raises an error if the operator in the transfer intent is not registered.\n    // @param _intent The intent to validate\n    modifier operatorIsRegistered(TransferIntent calldata _intent) {\n        if (feeDestinations[_intent.operator] == address(0)) revert OperatorNotRegistered();\n\n        _;\n    }\n\n    modifier exactValueSent(TransferIntent calldata _intent) {\n        // Make sure the correct value was sent\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n        if (msg.value > neededAmount) {\n            revert InvalidNativeAmount(int256(msg.value - neededAmount));\n        } else if (msg.value < neededAmount) {\n            revert InvalidNativeAmount(-int256(neededAmount - msg.value));\n        }\n\n        _;\n    }\n\n    // @inheritdoc ITransfers\n    function transferNative(TransferIntent calldata _intent)\n        external\n        payable\n        override\n        nonReentrant\n        whenNotPaused\n        validIntent(_intent, _msgSender())\n        operatorIsRegistered(_intent)\n        exactValueSent(_intent)\n    {\n        // Make sure the recipient wants the native currency\n        if (_intent.recipientCurrency != NATIVE_CURRENCY) revert IncorrectCurrency(NATIVE_CURRENCY);\n\n        if (msg.value > 0) {\n            // Complete the payment\n            transferFundsToDestinations(_intent);\n        }\n\n        succeedPayment(_intent, msg.value, NATIVE_CURRENCY, _msgSender());\n    }\n\n    // @inheritdoc ITransfers\n    function transferToken(\n        TransferIntent calldata _intent,\n        Permit2SignatureTransferData calldata _signatureTransferData\n    ) external override nonReentrant whenNotPaused validIntent(_intent, _msgSender()) operatorIsRegistered(_intent) {\n        // Make sure the recipient wants a token and the payer is sending it\n        if (\n            _intent.recipientCurrency == NATIVE_CURRENCY ||\n            _signatureTransferData.permit.permitted.token != _intent.recipientCurrency\n        ) {\n            revert IncorrectCurrency(_signatureTransferData.permit.permitted.token);\n        }\n\n        // Make sure the payer has enough of the payment token\n        IERC20 erc20 = IERC20(_intent.recipientCurrency);\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n        uint256 payerBalance = erc20.balanceOf(_msgSender());\n        if (payerBalance < neededAmount) {\n            revert InsufficientBalance(neededAmount - payerBalance);\n        }\n\n        if (neededAmount > 0) {\n            // Make sure the payer is transferring the right amount to this contract\n            if (\n                _signatureTransferData.transferDetails.to != address(this) ||\n                _signatureTransferData.transferDetails.requestedAmount != neededAmount\n            ) {\n                revert InvalidTransferDetails();\n            }\n\n            // Record our balance before (most likely zero) to detect fee-on-transfer tokens\n            uint256 balanceBefore = erc20.balanceOf(address(this));\n\n            // Transfer the payment token to this contract\n            permit2.permitTransferFrom(\n                _signatureTransferData.permit,\n                _signatureTransferData.transferDetails,\n                _msgSender(),\n                _signatureTransferData.signature\n            );\n\n            // Make sure this is not a fee-on-transfer token\n            revertIfInexactTransfer(neededAmount, balanceBefore, erc20, address(this));\n\n            // Complete the payment\n            transferFundsToDestinations(_intent);\n        }\n\n        succeedPayment(_intent, neededAmount, _intent.recipientCurrency, _msgSender());\n    }\n\n    // @inheritdoc ITransfers\n    function transferTokenPreApproved(TransferIntent calldata _intent)\n        external\n        override\n        nonReentrant\n        whenNotPaused\n        validIntent(_intent, _msgSender())\n        operatorIsRegistered(_intent)\n    {\n        // Make sure the recipient wants a token\n        if (_intent.recipientCurrency == NATIVE_CURRENCY) {\n            revert IncorrectCurrency(_intent.recipientCurrency);\n        }\n\n        // Make sure the payer has enough of the payment token\n        IERC20 erc20 = IERC20(_intent.recipientCurrency);\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n        uint256 payerBalance = erc20.balanceOf(_msgSender());\n        if (payerBalance < neededAmount) {\n            revert InsufficientBalance(neededAmount - payerBalance);\n        }\n\n        // Make sure the payer has approved this contract for a sufficient transfer\n        uint256 allowance = erc20.allowance(_msgSender(), address(this));\n        if (allowance < neededAmount) {\n            revert InsufficientAllowance(neededAmount - allowance);\n        }\n\n        if (neededAmount > 0) {\n            // Record our balance before (most likely zero) to detect fee-on-transfer tokens\n            uint256 balanceBefore = erc20.balanceOf(address(this));\n\n            // Transfer the payment token to this contract\n            erc20.safeTransferFrom(_msgSender(), address(this), neededAmount);\n\n            // Make sure this is not a fee-on-transfer token\n            revertIfInexactTransfer(neededAmount, balanceBefore, erc20, address(this));\n\n            // Complete the payment\n            transferFundsToDestinations(_intent);\n        }\n\n        succeedPayment(_intent, neededAmount, _intent.recipientCurrency, _msgSender());\n    }\n\n    // @inheritdoc ITransfers\n    // @dev Wraps msg.value into wrapped token and transfers to recipient.\n    function wrapAndTransfer(TransferIntent calldata _intent)\n        external\n        payable\n        override\n        nonReentrant\n        whenNotPaused\n        validIntent(_intent, _msgSender())\n        operatorIsRegistered(_intent)\n        exactValueSent(_intent)\n    {\n        // Make sure the recipient wants to receive the wrapped native currency\n        if (_intent.recipientCurrency != address(wrappedNativeCurrency)) {\n            revert IncorrectCurrency(NATIVE_CURRENCY);\n        }\n\n        if (msg.value > 0) {\n            // Wrap the sent native currency\n            wrappedNativeCurrency.deposit{value: msg.value}();\n\n            // Complete the payment\n            transferFundsToDestinations(_intent);\n        }\n\n        succeedPayment(_intent, msg.value, NATIVE_CURRENCY, _msgSender());\n    }\n\n    // @inheritdoc ITransfers\n    // @dev Requires _msgSender() to have approved this contract to use the wrapped token.\n    // @dev Unwraps into native token and transfers native token (e.g. ETH) to _intent.recipient.\n    function unwrapAndTransfer(\n        TransferIntent calldata _intent,\n        Permit2SignatureTransferData calldata _signatureTransferData\n    ) external override nonReentrant whenNotPaused validIntent(_intent, _msgSender()) operatorIsRegistered(_intent) {\n        // Make sure the recipient wants the native currency and that the payer is\n        // sending the wrapped native currency\n        if (\n            _intent.recipientCurrency != NATIVE_CURRENCY ||\n            _signatureTransferData.permit.permitted.token != address(wrappedNativeCurrency)\n        ) {\n            revert IncorrectCurrency(_signatureTransferData.permit.permitted.token);\n        }\n\n        // Make sure the payer has enough of the wrapped native currency\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n        uint256 payerBalance = wrappedNativeCurrency.balanceOf(_msgSender());\n        if (payerBalance < neededAmount) {\n            revert InsufficientBalance(neededAmount - payerBalance);\n        }\n\n        if (neededAmount > 0) {\n            // Make sure the payer is transferring the right amount of the wrapped native currency to the contract\n            if (\n                _signatureTransferData.transferDetails.to != address(this) ||\n                _signatureTransferData.transferDetails.requestedAmount != neededAmount\n            ) {\n                revert InvalidTransferDetails();\n            }\n\n            // Transfer the payer's wrapped native currency to the contract\n            permit2.permitTransferFrom(\n                _signatureTransferData.permit,\n                _signatureTransferData.transferDetails,\n                _msgSender(),\n                _signatureTransferData.signature\n            );\n\n            // Complete the payment\n            unwrapAndTransferFundsToDestinations(_intent);\n        }\n\n        succeedPayment(_intent, neededAmount, address(wrappedNativeCurrency), _msgSender());\n    }\n\n    // @inheritdoc ITransfers\n    // @dev Requires _msgSender() to have approved this contract to use the wrapped token.\n    // @dev Unwraps into native token and transfers native token (e.g. ETH) to _intent.recipient.\n    function unwrapAndTransferPreApproved(TransferIntent calldata _intent)\n        external\n        override\n        nonReentrant\n        whenNotPaused\n        validIntent(_intent, _msgSender())\n        operatorIsRegistered(_intent)\n    {\n        // Make sure the recipient wants the native currency\n        if (_intent.recipientCurrency != NATIVE_CURRENCY) {\n            revert IncorrectCurrency(address(wrappedNativeCurrency));\n        }\n\n        // Make sure the payer has enough of the wrapped native currency\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n        uint256 payerBalance = wrappedNativeCurrency.balanceOf(_msgSender());\n        if (payerBalance < neededAmount) {\n            revert InsufficientBalance(neededAmount - payerBalance);\n        }\n\n        // Make sure the payer has approved this contract for a sufficient transfer\n        uint256 allowance = wrappedNativeCurrency.allowance(_msgSender(), address(this));\n        if (allowance < neededAmount) {\n            revert InsufficientAllowance(neededAmount - allowance);\n        }\n\n        if (neededAmount > 0) {\n            // Transfer the payer's wrapped native currency to the contract\n            wrappedNativeCurrency.safeTransferFrom(_msgSender(), address(this), neededAmount);\n\n            // Complete the payment\n            unwrapAndTransferFundsToDestinations(_intent);\n        }\n\n        succeedPayment(_intent, neededAmount, address(wrappedNativeCurrency), _msgSender());\n    }\n\n    /*------------------------------------------------------------------*\\\n    | Swap and Transfer\n    \\*------------------------------------------------------------------*/\n\n    // @inheritdoc ITransfers\n    function swapAndTransferUniswapV3Native(TransferIntent calldata _intent, uint24 poolFeesTier)\n        external\n        payable\n        override\n        nonReentrant\n        whenNotPaused\n        validIntent(_intent, _msgSender())\n        operatorIsRegistered(_intent)\n    {\n        // Make sure a swap is actually required, otherwise the payer should use `wrapAndTransfer` or `transferNative`\n        if (\n            _intent.recipientCurrency == NATIVE_CURRENCY || _intent.recipientCurrency == address(wrappedNativeCurrency)\n        ) {\n            revert IncorrectCurrency(NATIVE_CURRENCY);\n        }\n\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n\n        uint256 amountSwapped = 0;\n        if (neededAmount > 0) {\n            // Perform the swap\n            amountSwapped = swapTokens(_intent, address(wrappedNativeCurrency), msg.value, poolFeesTier);\n        }\n\n        // Complete the payment\n        succeedPayment(_intent, amountSwapped, NATIVE_CURRENCY, _msgSender());\n    }\n\n    // @inheritdoc ITransfers\n    function swapAndTransferUniswapV3Token(\n        TransferIntent calldata _intent,\n        Permit2SignatureTransferData calldata _signatureTransferData,\n        uint24 poolFeesTier\n    ) external override nonReentrant whenNotPaused validIntent(_intent, _msgSender()) operatorIsRegistered(_intent) {\n        IERC20 tokenIn = IERC20(_signatureTransferData.permit.permitted.token);\n\n        // Make sure a swap is actually required\n        if (address(tokenIn) == _intent.recipientCurrency) {\n            revert IncorrectCurrency(address(tokenIn));\n        }\n\n        // Make sure the transfer is to this contract\n        if (_signatureTransferData.transferDetails.to != address(this)) {\n            revert InvalidTransferDetails();\n        }\n\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n        uint256 maxWillingToPay = _signatureTransferData.transferDetails.requestedAmount;\n\n        uint256 amountSwapped = 0;\n        if (neededAmount > 0) {\n            // Record our balance before (most likely zero) to detect fee-on-transfer tokens\n            uint256 balanceBefore = tokenIn.balanceOf(address(this));\n\n            // Transfer the payer's tokens to this contract\n            permit2.permitTransferFrom(\n                _signatureTransferData.permit,\n                _signatureTransferData.transferDetails,\n                _msgSender(),\n                _signatureTransferData.signature\n            );\n\n            // Make sure this is not a fee-on-transfer token\n            revertIfInexactTransfer(maxWillingToPay, balanceBefore, tokenIn, address(this));\n\n            // Perform the swap\n            amountSwapped = swapTokens(_intent, address(tokenIn), maxWillingToPay, poolFeesTier);\n        }\n\n        // Complete the payment\n        succeedPayment(_intent, amountSwapped, address(tokenIn), _msgSender());\n    }\n\n    // @inheritdoc ITransfers\n    function swapAndTransferUniswapV3TokenPreApproved(\n        TransferIntent calldata _intent,\n        address _tokenIn,\n        uint256 maxWillingToPay,\n        uint24 poolFeesTier\n    ) external override nonReentrant whenNotPaused validIntent(_intent, _msgSender()) operatorIsRegistered(_intent) {\n        IERC20 tokenIn = IERC20(_tokenIn);\n\n        // Make sure a swap is actually required\n        if (address(tokenIn) == _intent.recipientCurrency) {\n            revert IncorrectCurrency(address(tokenIn));\n        }\n\n        // Make sure the payer has enough of the payment token\n        uint256 payerBalance = tokenIn.balanceOf(_msgSender());\n        if (payerBalance < maxWillingToPay) {\n            revert InsufficientBalance(maxWillingToPay - payerBalance);\n        }\n\n        // Make sure the payer has approved this contract for a sufficient transfer\n        uint256 allowance = tokenIn.allowance(_msgSender(), address(this));\n        if (allowance < maxWillingToPay) {\n            revert InsufficientAllowance(maxWillingToPay - allowance);\n        }\n\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n\n        uint256 amountSwapped = 0;\n        if (neededAmount > 0) {\n            // Record our balance before (most likely zero) to detect fee-on-transfer tokens\n            uint256 balanceBefore = tokenIn.balanceOf(address(this));\n\n            // Transfer the payment token to this contract\n            tokenIn.safeTransferFrom(_msgSender(), address(this), maxWillingToPay);\n\n            // Make sure this is not a fee-on-transfer token\n            revertIfInexactTransfer(maxWillingToPay, balanceBefore, tokenIn, address(this));\n\n            // Perform the swap\n            amountSwapped = swapTokens(_intent, address(tokenIn), maxWillingToPay, poolFeesTier);\n        }\n\n        // Complete the payment\n        succeedPayment(_intent, amountSwapped, address(tokenIn), _msgSender());\n    }\n\n    // @inheritdoc ITransfers\n    function subsidizedTransferToken(\n        TransferIntent calldata _intent,\n        EIP2612SignatureTransferData calldata _signatureTransferData\n    )\n        external\n        override\n        nonReentrant\n        whenNotPaused\n        validIntent(_intent, _signatureTransferData.owner)\n        operatorIsRegistered(_intent)\n    {\n        // Make sure the recipient wants a token\n        if (_intent.recipientCurrency == NATIVE_CURRENCY) {\n            revert IncorrectCurrency(_intent.recipientCurrency);\n        }\n\n        // Check the balance of the payer\n        IERC20 erc20 = IERC20(_intent.recipientCurrency);\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n        uint256 payerBalance = erc20.balanceOf(_signatureTransferData.owner);\n        if (payerBalance < neededAmount) {\n            revert InsufficientBalance(neededAmount - payerBalance);\n        }\n\n        // Permit this contract to spend the payer's tokens\n        IERC7597(_intent.recipientCurrency).permit({\n            owner: _signatureTransferData.owner,\n            spender: address(this),\n            value: neededAmount,\n            deadline: _intent.deadline,\n            signature: _signatureTransferData.signature\n        });\n\n        // Check the payer has approved this contract for a sufficient transfer\n        uint256 allowance = erc20.allowance(_signatureTransferData.owner, address(this));\n        if (allowance < neededAmount) {\n            revert InsufficientAllowance(neededAmount - allowance);\n        }\n\n        if (neededAmount > 0) {\n            // Record our balance before (most likely zero) to detect fee-on-transfer tokens\n            uint256 balanceBefore = erc20.balanceOf(address(this));\n\n            // Transfer the payment token to this contract\n            erc20.safeTransferFrom(_signatureTransferData.owner, address(this), neededAmount);\n\n            // Make sure this is not a fee-on-transfer token\n            revertIfInexactTransfer(neededAmount, balanceBefore, erc20, address(this));\n\n            // Complete the payment\n            transferFundsToDestinations(_intent);\n        }\n\n        succeedPayment(_intent, neededAmount, _intent.recipientCurrency, _signatureTransferData.owner);\n    }\n\n    function swapTokens(\n        TransferIntent calldata _intent,\n        address tokenIn,\n        uint256 maxAmountWillingToPay,\n        uint24 poolFeesTier\n    ) internal returns (uint256) {\n        // If the seller is requesting native currency, we need to swap for the wrapped\n        // version of that currency first, then unwrap it and send it to the seller.\n        address tokenOut = _intent.recipientCurrency == NATIVE_CURRENCY\n            ? address(wrappedNativeCurrency)\n            : _intent.recipientCurrency;\n\n        // Figure out the total output needed from the swap\n        uint256 neededAmount = _intent.recipientAmount + _intent.feeAmount;\n\n        // Parameters and shared inputs for the universal router\n        bytes memory uniswap_commands;\n        bytes[] memory uniswap_inputs;\n        bytes memory swapPath = abi.encodePacked(tokenOut, poolFeesTier, tokenIn);\n        bytes memory swapParams = abi.encode(address(uniswap), neededAmount, maxAmountWillingToPay, swapPath, false);\n        bytes memory transferToRecipient = abi.encode(\n            _intent.recipientCurrency,\n            _intent.recipient,\n            _intent.recipientAmount\n        );\n        bytes memory collectFees = abi.encode(\n            _intent.recipientCurrency,\n            feeDestinations[_intent.operator],\n            _intent.feeAmount\n        );\n\n        // The payer's and router's balances before this transaction, used to calculate the amount consumed by the swap\n        uint256 payerBalanceBefore;\n        uint256 routerBalanceBefore;\n\n        // The fee and recipient balances of the output token, to detect fee-on-transfer tokens\n        uint256 feeBalanceBefore;\n        uint256 recipientBalanceBefore;\n\n        // Populate the commands and inputs for the universal router\n        if (msg.value > 0) {\n            payerBalanceBefore = _msgSender().balance + msg.value;\n            routerBalanceBefore = address(uniswap).balance + IERC20(wrappedNativeCurrency).balanceOf(address(uniswap));\n            feeBalanceBefore = IERC20(tokenOut).balanceOf(feeDestinations[_intent.operator]);\n            recipientBalanceBefore = IERC20(tokenOut).balanceOf(_intent.recipient);\n\n            // Paying with ETH, merchant wants tokenOut\n            uniswap_commands = abi.encodePacked(\n                bytes1(uint8(UniswapCommands.WRAP_ETH)),\n                bytes1(uint8(UniswapCommands.V3_SWAP_EXACT_OUT)),\n                bytes1(uint8(UniswapCommands.TRANSFER)),\n                bytes1(uint8(UniswapCommands.TRANSFER)),\n                bytes1(uint8(UniswapCommands.UNWRAP_WETH)), // for the payer refund\n                bytes1(uint8(UniswapCommands.SWEEP))\n            );\n            uniswap_inputs = new bytes[](6);\n            uniswap_inputs[0] = abi.encode(address(uniswap), msg.value);\n            uniswap_inputs[1] = swapParams;\n            uniswap_inputs[2] = collectFees;\n            uniswap_inputs[3] = transferToRecipient;\n            uniswap_inputs[4] = abi.encode(address(uniswap), 0);\n            uniswap_inputs[5] = abi.encode(UniswapConstants.ETH, _msgSender(), 0);\n        } else {\n            // No need to check fee/recipient balance of the output token before,\n            // since we know WETH and ETH are not fee-on-transfer\n            payerBalanceBefore = IERC20(tokenIn).balanceOf(_msgSender()) + maxAmountWillingToPay;\n            routerBalanceBefore = IERC20(tokenIn).balanceOf(address(uniswap));\n\n            if (_intent.recipientCurrency == NATIVE_CURRENCY) {\n                // Paying with token, merchant wants ETH\n                uniswap_commands = abi.encodePacked(\n                    bytes1(uint8(UniswapCommands.V3_SWAP_EXACT_OUT)),\n                    bytes1(uint8(UniswapCommands.UNWRAP_WETH)), // for the recipient\n                    bytes1(uint8(UniswapCommands.TRANSFER)),\n                    bytes1(uint8(UniswapCommands.TRANSFER)),\n                    bytes1(uint8(UniswapCommands.SWEEP))\n                );\n                uniswap_inputs = new bytes[](5);\n                uniswap_inputs[0] = swapParams;\n                uniswap_inputs[1] = abi.encode(address(uniswap), neededAmount);\n                uniswap_inputs[2] = collectFees;\n                uniswap_inputs[3] = transferToRecipient;\n                uniswap_inputs[4] = abi.encode(tokenIn, _msgSender(), 0);\n            } else {\n                feeBalanceBefore = IERC20(tokenOut).balanceOf(feeDestinations[_intent.operator]);\n                recipientBalanceBefore = IERC20(tokenOut).balanceOf(_intent.recipient);\n\n                // Paying with token, merchant wants tokenOut\n                uniswap_commands = abi.encodePacked(\n                    bytes1(uint8(UniswapCommands.V3_SWAP_EXACT_OUT)),\n                    bytes1(uint8(UniswapCommands.TRANSFER)),\n                    bytes1(uint8(UniswapCommands.TRANSFER)),\n                    bytes1(uint8(UniswapCommands.SWEEP))\n                );\n                uniswap_inputs = new bytes[](4);\n                uniswap_inputs[0] = swapParams;\n                uniswap_inputs[1] = collectFees;\n                uniswap_inputs[2] = transferToRecipient;\n                uniswap_inputs[3] = abi.encode(tokenIn, _msgSender(), 0);\n            }\n\n            // Send the input tokens to Uniswap for the swap\n            IERC20(tokenIn).safeTransfer(address(uniswap), maxAmountWillingToPay);\n        }\n\n        // Perform the swap\n        try uniswap.execute{value: msg.value}(uniswap_commands, uniswap_inputs, _intent.deadline) {\n            // Disallow fee-on-transfer tokens as the output token, since we want to guarantee exact settlement\n            if (_intent.recipientCurrency != NATIVE_CURRENCY) {\n                revertIfInexactTransfer(\n                    _intent.feeAmount,\n                    feeBalanceBefore,\n                    IERC20(tokenOut),\n                    feeDestinations[_intent.operator]\n                );\n                revertIfInexactTransfer(\n                    _intent.recipientAmount,\n                    recipientBalanceBefore,\n                    IERC20(tokenOut),\n                    _intent.recipient\n                );\n            }\n\n            // Calculate and return how much of the input token was consumed by the swap. The router\n            // could have had a balance of the input token prior to this transaction, which would have\n            // been swept to the payer. This amount, if any, must be accounted for so we don't underflow\n            // and assume that negative amount of the input token was consumed by the swap.\n            uint256 payerBalanceAfter;\n            uint256 routerBalanceAfter;\n            if (msg.value > 0) {\n                payerBalanceAfter = _msgSender().balance;\n                routerBalanceAfter =\n                    address(uniswap).balance +\n                    IERC20(wrappedNativeCurrency).balanceOf(address(uniswap));\n            } else {\n                payerBalanceAfter = IERC20(tokenIn).balanceOf(_msgSender());\n                routerBalanceAfter = IERC20(tokenIn).balanceOf(address(uniswap));\n            }\n            return (payerBalanceBefore + routerBalanceBefore) - (payerBalanceAfter + routerBalanceAfter);\n        } catch Error(string memory reason) {\n            revert SwapFailedString(reason);\n        } catch (bytes memory reason) {\n            bytes32 reasonHash = keccak256(reason);\n            if (reasonHash == V3_INVALID_SWAP) {\n                revert SwapFailedString(\"V3InvalidSwap\");\n            } else if (reasonHash == V3_TOO_LITTLE_RECEIVED) {\n                revert SwapFailedString(\"V3TooLittleReceived\");\n            } else if (reasonHash == V3_TOO_MUCH_REQUESTED) {\n                revert SwapFailedString(\"V3TooMuchRequested\");\n            } else if (reasonHash == V3_INVALID_AMOUNT_OUT) {\n                revert SwapFailedString(\"V3InvalidAmountOut\");\n            } else if (reasonHash == V3_INVALID_CALLER) {\n                revert SwapFailedString(\"V3InvalidCaller\");\n            } else {\n                revert SwapFailedBytes(reason);\n            }\n        }\n    }\n\n    function transferFundsToDestinations(TransferIntent calldata _intent) internal {\n        if (_intent.recipientCurrency == NATIVE_CURRENCY) {\n            if (_intent.recipientAmount > 0) {\n                sendNative(_intent.recipient, _intent.recipientAmount, false);\n            }\n            if (_intent.feeAmount > 0) {\n                sendNative(feeDestinations[_intent.operator], _intent.feeAmount, false);\n            }\n        } else {\n            IERC20 requestedCurrency = IERC20(_intent.recipientCurrency);\n            if (_intent.recipientAmount > 0) {\n                requestedCurrency.safeTransfer(_intent.recipient, _intent.recipientAmount);\n            }\n            if (_intent.feeAmount > 0) {\n                requestedCurrency.safeTransfer(feeDestinations[_intent.operator], _intent.feeAmount);\n            }\n        }\n    }\n\n    function unwrapAndTransferFundsToDestinations(TransferIntent calldata _intent) internal {\n        uint256 amountToWithdraw = _intent.recipientAmount + _intent.feeAmount;\n        if (_intent.recipientCurrency == NATIVE_CURRENCY && amountToWithdraw > 0) {\n            wrappedNativeCurrency.withdraw(amountToWithdraw);\n        }\n        transferFundsToDestinations(_intent);\n    }\n\n    function succeedPayment(\n        TransferIntent calldata _intent,\n        uint256 spentAmount,\n        address spentCurrency,\n        address sender\n    ) internal {\n        processedTransferIntents[_intent.operator][_intent.id] = true;\n        emit Transferred(_intent.operator, _intent.id, _intent.recipient, sender, spentAmount, spentCurrency);\n    }\n\n    function sendNative(\n        address destination,\n        uint256 amount,\n        bool isRefund\n    ) internal {\n        (bool success, bytes memory data) = payable(destination).call{value: amount}(\"\");\n        if (!success) {\n            revert NativeTransferFailed(destination, amount, isRefund, data);\n        }\n    }\n\n    function revertIfInexactTransfer(\n        uint256 expectedDiff,\n        uint256 balanceBefore,\n        IERC20 token,\n        address target\n    ) internal view {\n        uint256 balanceAfter = token.balanceOf(target);\n        if (balanceAfter - balanceBefore != expectedDiff) {\n            revert InexactTransfer();\n        }\n    }\n\n    // @notice Registers an operator with a custom fee destination.\n    function registerOperatorWithFeeDestination(address _feeDestination) external {\n        feeDestinations[_msgSender()] = _feeDestination;\n\n        emit OperatorRegistered(_msgSender(), _feeDestination);\n    }\n\n    // @notice Registers an operator, using the operator's address as the fee destination.\n    function registerOperator() external {\n        feeDestinations[_msgSender()] = _msgSender();\n\n        emit OperatorRegistered(_msgSender(), _msgSender());\n    }\n\n    function unregisterOperator() external {\n        delete feeDestinations[_msgSender()];\n\n        emit OperatorUnregistered(_msgSender());\n    }\n\n    // @notice Allows the owner to pause the contract.\n    function pause() external onlyOwner {\n        _pause();\n    }\n\n    // @notice Allows the owner to un-pause the contract.\n    function unpause() external onlyOwner {\n        _unpause();\n    }\n\n    // @dev Required to be able to unwrap WETH\n    receive() external payable {\n        require(msg.sender == address(wrappedNativeCurrency), \"only payable for unwrapping\");\n    }\n}\n"
  },
  {
    "path": "contracts/utils/Sweepable.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.17;\n\nimport \"@openzeppelin/contracts/utils/Context.sol\";\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\n\n// @title Sweepable contract\n// @notice Implements a role that can sweep stuck funds to an address provided\n//   at the time of the call\nabstract contract Sweepable is Context, Ownable {\n    using SafeERC20 for IERC20;\n\n    // @dev The address of the current sweeper\n    address private _sweeper;\n\n    // @dev Restricts the caller to the current sweeper\n    modifier onlySweeper() {\n        require(sweeper() == _msgSender(), \"Sweepable: not the sweeper\");\n        _;\n    }\n\n    modifier notZero(address a) {\n        require(a != address(0), \"Sweepable: cannot be zero address\");\n        _;\n    }\n\n    // @dev Returns the current sweeper\n    function sweeper() public view virtual returns (address) {\n        return _sweeper;\n    }\n\n    // @dev Sets the sweeper\n    // @notice To remove the sweeper role entirely, set this to the zero address.\n    function setSweeper(address newSweeper) public virtual onlyOwner notZero(newSweeper) {\n        _sweeper = newSweeper;\n    }\n\n    // @dev Sweeps the entire ETH balance to `destination`\n    function sweepETH(address payable destination) public virtual onlySweeper notZero(destination) {\n        uint256 balance = address(this).balance;\n        require(balance > 0, \"Sweepable: zero balance\");\n        (bool success, ) = destination.call{value: balance}(\"\");\n        require(success, \"Sweepable: transfer error\");\n    }\n\n    // @dev Sweeps a specific ETH `amount` to `destination`\n    function sweepETHAmount(address payable destination, uint256 amount)\n        public\n        virtual\n        onlySweeper\n        notZero(destination)\n    {\n        uint256 balance = address(this).balance;\n        require(balance >= amount, \"Sweepable: insufficient balance\");\n        (bool success, ) = destination.call{value: amount}(\"\");\n        require(success, \"Sweepable: transfer error\");\n    }\n\n    // @dev Sweeps the entire token balance to `destination`\n    function sweepToken(address _token, address destination) public virtual onlySweeper notZero(destination) {\n        IERC20 token = IERC20(_token);\n        uint256 balance = token.balanceOf(address(this));\n        require(balance > 0, \"Sweepable: zero balance\");\n        token.safeTransfer(destination, balance);\n    }\n\n    // @dev Sweeps a specific token `amount` to `destination`\n    function sweepTokenAmount(\n        address _token,\n        address destination,\n        uint256 amount\n    ) public virtual onlySweeper notZero(destination) {\n        IERC20 token = IERC20(_token);\n        uint256 balance = token.balanceOf(address(this));\n        require(balance >= amount, \"Sweepable: insufficient balance\");\n        token.safeTransfer(destination, amount);\n    }\n}\n"
  }
]