BatchConversionPayments

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": {
    "@openzeppelin/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
"
    },
    "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
"
    },
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @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);
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @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;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "contracts/BatchConversionPayments.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.4;\r
\r
import "./interfaces/IERC20ConversionProxy.sol";\r
import "./interfaces/IEthConversionProxy.sol";\r
import "./BatchNoConversionPayments.sol";\r
\r
/**\r
 * @title BatchConversionPayments\r
 * @notice This contract makes multiple conversion payments with references, in one transaction:\r
 *          - on:\r
 *              - ERC20 tokens: using Erc20ConversionProxy and ERC20FeeProxy\r
 *              - Native tokens: (e.g. ETH) using EthConversionProxy and EthereumFeeProxy\r
 *          - to: multiple addresses\r
 *          - fees: conversion proxy fees and additional batch conversion fees are paid to the same address.\r
 *         batchPayments is the main function to batch all kinds of payments at once.\r
 *         If one transaction of the batch fails, all transactions are reverted.\r
 * @dev batchPayments is the main function, but other batch payment functions are "public" in order to do\r
 *      gas optimization in some cases.\r
 */\r
contract BatchConversionPayments is BatchNoConversionPayments {\r
    using SafeERC20 for IERC20;\r
\r
    IERC20ConversionProxy public paymentErc20ConversionProxy;\r
    IEthConversionProxy public paymentNativeConversionProxy;\r
\r
    /** payerAuthorized is set to true to workaround the non-payable aspect in batch native conversion */\r
    bool private payerAuthorized = false;\r
\r
    /**\r
     * @dev Used by the batchPayments to handle information for heterogeneous batches, grouped by payment network:\r
     *  - paymentNetworkId: from 0 to 4, cf. `batchPayments()` method\r
     *  - requestDetails all the data required for conversion and no conversion requests to be paid\r
     */\r
    struct MetaDetail {\r
        uint256 paymentNetworkId;\r
        RequestDetail[] requestDetails;\r
    }\r
\r
    /**\r
     * @param _paymentErc20Proxy The ERC20 payment proxy address to use.\r
     * @param _paymentNativeProxy The native payment proxy address to use.\r
     * @param _paymentErc20ConversionProxy The ERC20 Conversion payment proxy address to use.\r
     * @param _paymentNativeConversionFeeProxy The native Conversion payment proxy address to use.\r
     * @param _chainlinkConversionPath The address of the conversion path contract.\r
     * @param _owner Owner of the contract.\r
     */\r
    constructor(\r
        address _paymentErc20Proxy,\r
        address _paymentNativeProxy,\r
        address _paymentErc20ConversionProxy,\r
        address _paymentNativeConversionFeeProxy,\r
        address _chainlinkConversionPath,\r
        address _owner\r
    )\r
        BatchNoConversionPayments(\r
            _paymentErc20Proxy,\r
            _paymentNativeProxy,\r
            _chainlinkConversionPath,\r
            _owner\r
        )\r
    {\r
        paymentErc20ConversionProxy = IERC20ConversionProxy(\r
            _paymentErc20ConversionProxy\r
        );\r
        paymentNativeConversionProxy = IEthConversionProxy(\r
            _paymentNativeConversionFeeProxy\r
        );\r
    }\r
\r
    /**\r
     * This contract is non-payable.\r
     * Making a Native payment with conversion requires the contract to accept incoming Native tokens.\r
     * @dev See the end of `paymentNativeConversionProxy.transferWithReferenceAndFee` where the leftover is given back.\r
     */\r
    receive() external payable override {\r
        require(payerAuthorized || msg.value == 0, "Non-payable");\r
    }\r
\r
    /**\r
     * @notice Batch payments on different payment networks at once.\r
     * @param metaDetails contains paymentNetworkId and requestDetails\r
     * - batchMultiERC20ConversionPayments, paymentNetworkId=0\r
     * - batchERC20Payments, paymentNetworkId=1\r
     * - batchMultiERC20Payments, paymentNetworkId=2\r
     * - batchNativePayments, paymentNetworkId=3\r
     * - batchNativeConversionPayments, paymentNetworkId=4\r
     * If metaDetails use paymentNetworkId = 4, it must be at the end of the list, or the transaction can be reverted.\r
     * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
     *                   For batch native, mock an array of array to apply the limit, e.g: [[]]\r
     *                   Without paths, there is not limitation, neither for the batch native functions.\r
     * @param feeAddress The address where fees should be paid.\r
     * @dev Use pathsToUSD only if you are pretty sure the batch fees will higher than the\r
     *      USD limit batchFeeAmountUSDLimit, because it increase gas consumption.\r
     *      batchPayments only reduces gas consumption when using more than a single payment network.\r
     *      For single payment network payments, it is more efficient to use the suited batch function.\r
     */\r
    function batchPayments(\r
        MetaDetail[] calldata metaDetails,\r
        address[][] calldata pathsToUSD,\r
        address feeAddress\r
    ) external payable {\r
        require(metaDetails.length < 6, "more than 5 metaDetails");\r
\r
        uint256 batchFeeAmountUSD = 0;\r
        for (uint256 i = 0; i < metaDetails.length; i++) {\r
            MetaDetail calldata metaDetail = metaDetails[i];\r
            if (metaDetail.paymentNetworkId == 0) {\r
                batchFeeAmountUSD += _batchMultiERC20ConversionPayments(\r
                    metaDetail.requestDetails,\r
                    batchFeeAmountUSD,\r
                    pathsToUSD,\r
                    feeAddress\r
                );\r
            } else if (metaDetail.paymentNetworkId == 1) {\r
                batchFeeAmountUSD += _batchERC20Payments(\r
                    metaDetail.requestDetails,\r
                    pathsToUSD,\r
                    batchFeeAmountUSD,\r
                    payable(feeAddress)\r
                );\r
            } else if (metaDetail.paymentNetworkId == 2) {\r
                batchFeeAmountUSD += _batchMultiERC20Payments(\r
                    metaDetail.requestDetails,\r
                    pathsToUSD,\r
                    batchFeeAmountUSD,\r
                    feeAddress\r
                );\r
            } else if (metaDetail.paymentNetworkId == 3) {\r
                if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {\r
                    // Set to false only if batchNativeConversionPayments is called after this function\r
                    transferBackRemainingNativeTokens = false;\r
                }\r
                batchFeeAmountUSD += _batchNativePayments(\r
                    metaDetail.requestDetails,\r
                    pathsToUSD.length == 0,\r
                    batchFeeAmountUSD,\r
                    payable(feeAddress)\r
                );\r
                if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {\r
                    transferBackRemainingNativeTokens = true;\r
                }\r
            } else if (metaDetail.paymentNetworkId == 4) {\r
                batchFeeAmountUSD += _batchNativeConversionPayments(\r
                    metaDetail.requestDetails,\r
                    pathsToUSD.length == 0,\r
                    batchFeeAmountUSD,\r
                    payable(feeAddress)\r
                );\r
            } else {\r
                revert("Wrong paymentNetworkId");\r
            }\r
        }\r
    }\r
\r
    /**\r
     * @notice Send a batch of ERC20 payments with amounts based on a request\r
     * currency (e.g. fiat), with fees and paymentReferences to multiple accounts, with multiple tokens.\r
     * @param requestDetails List of ERC20 requests denominated in fiat to pay.\r
     * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
     *                   Without paths, there is not a fee limitation, and it consumes less gas.\r
     * @param feeAddress The fee recipient.\r
     */\r
    function batchMultiERC20ConversionPayments(\r
        RequestDetail[] calldata requestDetails,\r
        address[][] calldata pathsToUSD,\r
        address feeAddress\r
    ) public returns (uint256) {\r
        return\r
            _batchMultiERC20ConversionPayments(\r
                requestDetails,\r
                0,\r
                pathsToUSD,\r
                feeAddress\r
            );\r
    }\r
\r
    /**\r
     * @notice Send a batch of Native conversion payments with fees and paymentReferences to multiple accounts.\r
     *         If one payment fails, the whole batch is reverted.\r
     * @param requestDetails List of native requests denominated in fiat to pay.\r
     * @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.\r
     * @param feeAddress The fee recipient.\r
     * @dev It uses NativeConversionProxy (EthereumConversionProxy) to pay an invoice and fees.\r
     *      Please:\r
     *        Note that if there is not enough Native token attached to the function call,\r
     *        the following error is thrown: "revert paymentProxy transferExactEthWithReferenceAndFee failed"\r
     */\r
    function batchNativeConversionPayments(\r
        RequestDetail[] calldata requestDetails,\r
        bool skipFeeUSDLimit,\r
        address payable feeAddress\r
    ) public payable returns (uint256) {\r
        return\r
            _batchNativeConversionPayments(\r
                requestDetails,\r
                skipFeeUSDLimit,\r
                0,\r
                feeAddress\r
            );\r
    }\r
\r
    /**\r
     * @notice Send a batch of ERC20 payments with amounts based on a request\r
     * currency (e.g. fiat), with fees and paymentReferences to multiple accounts, with multiple tokens.\r
     * @param requestDetails List of ERC20 requests denominated in fiat to pay.\r
     * @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
     * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
     *                   Without paths, there is not a fee limitation, and it consumes less gas.\r
     * @param feeAddress The fee recipient.\r
     */\r
    function _batchMultiERC20ConversionPayments(\r
        RequestDetail[] calldata requestDetails,\r
        uint256 batchFeeAmountUSD,\r
        address[][] calldata pathsToUSD,\r
        address feeAddress\r
    ) private returns (uint256) {\r
        Token[] memory uTokens = getUTokens(requestDetails);\r
\r
        IERC20 requestedToken;\r
        // For each token: check allowance, transfer funds on the contract and approve the paymentProxy to spend if needed\r
        for (\r
            uint256 k = 0;\r
            k < uTokens.length && uTokens[k].amountAndFee > 0;\r
            k++\r
        ) {\r
            uTokens[k].batchFeeAmount =\r
                (uTokens[k].amountAndFee * batchFee) /\r
                feeDenominator;\r
            requestedToken = IERC20(uTokens[k].tokenAddress);\r
            transferToContract(\r
                requestedToken,\r
                uTokens[k].amountAndFee,\r
                uTokens[k].batchFeeAmount,\r
                address(paymentErc20ConversionProxy)\r
            );\r
        }\r
\r
        // Batch pays the requests using Erc20ConversionFeeProxy\r
        for (uint256 i = 0; i < requestDetails.length; i++) {\r
            RequestDetail calldata rD = requestDetails[i];\r
            paymentErc20ConversionProxy.transferFromWithReferenceAndFee(\r
                rD.recipient,\r
                rD.requestAmount,\r
                rD.path,\r
                rD.paymentReference,\r
                rD.feeAmount,\r
                feeAddress,\r
                rD.maxToSpend,\r
                rD.maxRateTimespan\r
            );\r
        }\r
\r
        // Batch sends back to the payer the tokens not spent and pays the batch fee\r
        for (\r
            uint256 k = 0;\r
            k < uTokens.length && uTokens[k].amountAndFee > 0;\r
            k++\r
        ) {\r
            requestedToken = IERC20(uTokens[k].tokenAddress);\r
\r
            // Batch sends back to the payer the tokens not spent = excessAmount\r
            // excessAmount = maxToSpend - reallySpent, which is equal to the remaining tokens on the contract\r
            uint256 excessAmount = requestedToken.balanceOf(address(this));\r
            if (excessAmount > 0) {\r
                requestedToken.safeTransfer(msg.sender, excessAmount);\r
            }\r
\r
            // Calculate batch fee to pay\r
            uint256 batchFeeToPay = ((uTokens[k].amountAndFee - excessAmount) *\r
                batchFee) / feeDenominator;\r
\r
            (batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
                batchFeeToPay,\r
                uTokens[k].tokenAddress,\r
                batchFeeAmountUSD,\r
                pathsToUSD\r
            );\r
\r
            // Payer pays the exact batch fees amount\r
            require(\r
                safeTransferFrom(\r
                    uTokens[k].tokenAddress,\r
                    feeAddress,\r
                    batchFeeToPay\r
                ),\r
                "Batch fee transferFrom() failed"\r
            );\r
        }\r
        return batchFeeAmountUSD;\r
    }\r
\r
    /**\r
     * @notice Send a batch of Native conversion payments with fees and paymentReferences to multiple accounts.\r
     *         If one payment fails, the whole batch is reverted.\r
     * @param requestDetails List of native requests denominated in fiat to pay.\r
     * @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.\r
     * @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
     * @param feeAddress The fee recipient.\r
     * @dev It uses NativeConversionProxy (EthereumConversionProxy) to pay an invoice and fees.\r
     *      Please:\r
     *        Note that if there is not enough Native token attached to the function call,\r
     *        the following error is thrown: "revert paymentProxy transferExactEthWithReferenceAndFee failed"\r
     */\r
    function _batchNativeConversionPayments(\r
        RequestDetail[] calldata requestDetails,\r
        bool skipFeeUSDLimit,\r
        uint256 batchFeeAmountUSD,\r
        address payable feeAddress\r
    ) private returns (uint256) {\r
        uint256 contractBalance = address(this).balance;\r
        payerAuthorized = true;\r
\r
        // Batch contract pays the requests through nativeConversionProxy\r
        for (uint256 i = 0; i < requestDetails.length; i++) {\r
            RequestDetail calldata rD = requestDetails[i];\r
            paymentNativeConversionProxy.transferWithReferenceAndFee{\r
                value: address(this).balance\r
            }(\r
                payable(rD.recipient),\r
                rD.requestAmount,\r
                rD.path,\r
                rD.paymentReference,\r
                rD.feeAmount,\r
                feeAddress,\r
                rD.maxRateTimespan\r
            );\r
        }\r
\r
        // Batch contract pays batch fee\r
        uint256 batchFeeToPay = (((contractBalance - address(this).balance)) *\r
            batchFee) / feeDenominator;\r
\r
        if (skipFeeUSDLimit == false) {\r
            (batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
                batchFeeToPay,\r
                pathsNativeToUSD[0][0],\r
                batchFeeAmountUSD,\r
                pathsNativeToUSD\r
            );\r
        }\r
\r
        require(\r
            address(this).balance >= batchFeeToPay,\r
            "Not enough funds for batch conversion fees"\r
        );\r
        feeAddress.transfer(batchFeeToPay);\r
\r
        // Batch contract transfers the remaining native tokens to the payer\r
        (bool sendBackSuccess, ) = payable(msg.sender).call{\r
            value: address(this).balance\r
        }("");\r
        require(sendBackSuccess, "Could not send remaining funds to the payer");\r
        payerAuthorized = false;\r
\r
        return batchFeeAmountUSD;\r
    }\r
\r
    /*\r
     * Admin functions to edit the conversion proxies address and fees.\r
     */\r
\r
    /**\r
     * @param _paymentErc20ConversionProxy The address of the ERC20 Conversion payment proxy to use.\r
     *        Update cautiously, the proxy has to match the invoice proxy.\r
     */\r
    function setPaymentErc20ConversionProxy(\r
        address _paymentErc20ConversionProxy\r
    ) external onlyOwner {\r
        paymentErc20ConversionProxy = IERC20ConversionProxy(\r
            _paymentErc20ConversionProxy\r
        );\r
    }\r
\r
    /**\r
     * @param _paymentNativeConversionProxy The address of the native Conversion payment proxy to use.\r
     *        Update cautiously, the proxy has to match the invoice proxy.\r
     */\r
    function setPaymentNativeConversionProxy(\r
        address _paymentNativeConversionProxy\r
    ) external onlyOwner {\r
        paymentNativeConversionProxy = IEthConversionProxy(\r
            _paymentNativeConversionProxy\r
        );\r
    }\r
}\r
"
    },
    "contracts/BatchNoConversionPayments.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.4;\r
\r
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
import "./lib/SafeERC20.sol";\r
import "@openzeppelin/contracts/access/Ownable.sol";\r
import "./interfaces/ERC20FeeProxy.sol";\r
import "./interfaces/EthereumFeeProxy.sol";\r
import "./ChainlinkConversionPath.sol";\r
\r
/**\r
 * @title BatchNoConversionPayments\r
 * @notice  This contract makes multiple payments with references, in one transaction:\r
 *          - on: ERC20 Payment Proxy and Native (ETH) Payment Proxy of the Request Network protocol\r
 *          - to: multiple addresses\r
 *          - fees: ERC20 and Native (ETH) proxies fees are paid to the same address\r
 *                  An additional batch fee is paid to the same address\r
 *         If one transaction of the batch fail, every transactions are reverted.\r
 * @dev It is a clone of BatchPayment.sol, with three main modifications:\r
 *         - function "receive" has one other condition: payerAuthorized\r
 *         - fees are now divided by 10_000 instead of 1_000 in previous version\r
 *         - batch payment functions have new names and are now public, instead of external\r
 */\r
contract BatchNoConversionPayments is Ownable {\r
    using SafeERC20 for IERC20;\r
\r
    IERC20FeeProxy public paymentErc20Proxy;\r
    IEthereumFeeProxy public paymentNativeProxy;\r
    ChainlinkConversionPath public chainlinkConversionPath;\r
\r
    /** Used to calculate batch fees: batchFee = 30 represent 0.30% of fee */\r
    uint16 public batchFee = 0;\r
    /** Used to calculate batch fees: divide batchFee by feeDenominator */\r
    uint16 internal feeDenominator = 10000;\r
    /** The amount of the batch fee cannot exceed a predefined amount in USD, e.g:\r
      batchFeeAmountUSDLimit = 150 * 1e8 represents $150 */\r
    uint64 public batchFeeAmountUSDLimit = 15000000000;\r
\r
    /** transferBackRemainingNativeTokens is set to false only if the payer use batchPayments\r
  and call both batchNativePayments and batchNativeConversionPayments */\r
    bool internal transferBackRemainingNativeTokens = true;\r
\r
    address public USDAddress = 0x775EB53d00DD0Acd3EC1696472105d579B9b386b;\r
    address public NativeAddress = 0xF5AF88e117747e87fC5929F2ff87221B1447652E;\r
    address[][] public pathsNativeToUSD;\r
\r
    /** Contains the address of a token, the sum of the amount and fees paid with it, and the batch fee amount */\r
    struct Token {\r
        address tokenAddress;\r
        uint256 amountAndFee;\r
        uint256 batchFeeAmount;\r
    }\r
\r
    /**\r
     * @dev All the information of a request, except the feeAddress\r
     *   recipient: Recipient address of the payment\r
     *   requestAmount: Request amount, in fiat for conversion payment\r
     *   path: Only for conversion payment: the conversion path\r
     *   paymentReference: Unique reference of the payment\r
     *   feeAmount: The fee amount, denominated in the first currency of `path` for conversion payment\r
     *   maxToSpend: Only for conversion payment:\r
     *               Maximum amount the payer wants to spend, denominated in the last currency of `path`:\r
     *                it includes fee proxy but NOT the batch fees to pay\r
     *   maxRateTimespan: Only for conversion payment:\r
     *                    Max acceptable times span for conversion rates, ignored if zero\r
     */\r
    struct RequestDetail {\r
        address recipient;\r
        uint256 requestAmount;\r
        address[] path;\r
        bytes paymentReference;\r
        uint256 feeAmount;\r
        uint256 maxToSpend;\r
        uint256 maxRateTimespan;\r
    }\r
\r
    /**\r
     * @param _paymentErc20Proxy The address to the ERC20 fee payment proxy to use.\r
     * @param _paymentNativeProxy The address to the Native fee payment proxy to use.\r
     * @param _chainlinkConversionPath The address of the conversion path contract.\r
     * @param _owner Owner of the contract.\r
     */\r
    constructor(\r
        address _paymentErc20Proxy,\r
        address _paymentNativeProxy,\r
        address _chainlinkConversionPath,\r
        address _owner\r
    ) {\r
        paymentErc20Proxy = IERC20FeeProxy(_paymentErc20Proxy);\r
        paymentNativeProxy = IEthereumFeeProxy(_paymentNativeProxy);\r
        chainlinkConversionPath = ChainlinkConversionPath(\r
            _chainlinkConversionPath\r
        );\r
        transferOwnership(_owner);\r
        batchFee = 0;\r
    }\r
\r
    /**\r
     * This contract is non-payable.\r
     * @dev See the end of `paymentNativeProxy.transferWithReferenceAndFee` where the leftover is given back.\r
     */\r
    receive() external payable virtual {\r
        require(msg.value == 0, "Non-payable");\r
    }\r
\r
    /**\r
     * @notice Gets batch fee\r
     */\r
    function getBatchFee() external view returns (uint256 data) {\r
        return batchFee;\r
    }\r
\r
    /**\r
     * @notice Send a batch of Native token payments with fees and paymentReferences to multiple accounts.\r
     *         If one payment fails, the whole batch reverts.\r
     * @param requestDetails List of Native tokens requests to pay.\r
     * @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.\r
     * @param feeAddress The fee recipient.\r
     * @dev It uses NativeFeeProxy (EthereumFeeProxy) to pay an invoice and fees with a payment reference.\r
     *      Make sure: msg.value >= sum(_amouts)+sum(_feeAmounts)+sumBatchFeeAmount\r
     */\r
    function batchNativePayments(\r
        RequestDetail[] calldata requestDetails,\r
        bool skipFeeUSDLimit,\r
        address payable feeAddress\r
    ) public payable returns (uint256) {\r
        return\r
            _batchNativePayments(\r
                requestDetails,\r
                skipFeeUSDLimit,\r
                0,\r
                payable(feeAddress)\r
            );\r
    }\r
\r
    /**\r
     * @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts.\r
     * @param requestDetails List of ERC20 requests to pay, with only one ERC20 token.\r
     * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
     *                   Without paths, there is not a fee limitation, and it consumes less gas.\r
     * @param feeAddress The fee recipient.\r
     * @dev Uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.\r
     *      Make sure this contract has enough allowance to spend the payer's token.\r
     *      Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.\r
     */\r
    function batchERC20Payments(\r
        RequestDetail[] calldata requestDetails,\r
        address[][] calldata pathsToUSD,\r
        address feeAddress\r
    ) public returns (uint256) {\r
        return _batchERC20Payments(requestDetails, pathsToUSD, 0, feeAddress);\r
    }\r
\r
    /**\r
     * @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts, with multiple tokens.\r
     * @param requestDetails List of ERC20 requests to pay.\r
     * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
     *                   Without paths, there is not a fee limitation, and it consumes less gas.\r
     * @param feeAddress The fee recipient.\r
     * @dev It uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.\r
     *      Make sure this contract has enough allowance to spend the payer's token.\r
     *      Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.\r
     */\r
    function batchMultiERC20Payments(\r
        RequestDetail[] calldata requestDetails,\r
        address[][] calldata pathsToUSD,\r
        address feeAddress\r
    ) public returns (uint256) {\r
        return\r
            _batchMultiERC20Payments(requestDetails, pathsToUSD, 0, feeAddress);\r
    }\r
\r
    /**\r
     * @notice Send a batch of Native token payments with fees and paymentReferences to multiple accounts.\r
     *         If one payment fails, the whole batch reverts.\r
     * @param requestDetails List of Native tokens requests to pay.\r
     * @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.\r
     * @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
     * @param feeAddress The fee recipient.\r
     * @dev It uses NativeFeeProxy (EthereumFeeProxy) to pay an invoice and fees with a payment reference.\r
     *      Make sure: msg.value >= sum(_amouts)+sum(_feeAmounts)+sumBatchFeeAmount\r
     */\r
    function _batchNativePayments(\r
        RequestDetail[] calldata requestDetails,\r
        bool skipFeeUSDLimit,\r
        uint256 batchFeeAmountUSD,\r
        address payable feeAddress\r
    ) internal returns (uint256) {\r
        // amount is used to get the total amount and then used as batch fee amount\r
        uint256 amount = 0;\r
\r
        // Batch contract pays the requests thourgh NativeFeeProxy (EthFeeProxy)\r
        for (uint256 i = 0; i < requestDetails.length; i++) {\r
            RequestDetail calldata rD = requestDetails[i];\r
            require(\r
                address(this).balance >= rD.requestAmount + rD.feeAmount,\r
                "Not enough funds"\r
            );\r
            amount += rD.requestAmount;\r
\r
            paymentNativeProxy.transferWithReferenceAndFee{\r
                value: rD.requestAmount + rD.feeAmount\r
            }(\r
                payable(rD.recipient),\r
                rD.paymentReference,\r
                rD.feeAmount,\r
                payable(feeAddress)\r
            );\r
        }\r
\r
        // amount is updated into batch fee amount\r
        amount = (amount * batchFee) / feeDenominator;\r
        if (skipFeeUSDLimit == false) {\r
            (amount, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
                amount,\r
                pathsNativeToUSD[0][0],\r
                batchFeeAmountUSD,\r
                pathsNativeToUSD\r
            );\r
        }\r
        // Check that batch contract has enough funds to pay batch fee\r
        require(\r
            address(this).balance >= amount,\r
            "Not enough funds for batch fee"\r
        );\r
        // Batch pays batch fee\r
        feeAddress.transfer(amount);\r
\r
        // Batch contract transfers the remaining Native tokens to the payer\r
        if (transferBackRemainingNativeTokens && address(this).balance > 0) {\r
            (bool sendBackSuccess, ) = payable(msg.sender).call{\r
                value: address(this).balance\r
            }("");\r
            require(\r
                sendBackSuccess,\r
                "Could not send remaining funds to the payer"\r
            );\r
        }\r
        return batchFeeAmountUSD;\r
    }\r
\r
    /**\r
     * @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts.\r
     * @param requestDetails List of ERC20 requests to pay, with only one ERC20 token.\r
     * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
     *                   Without paths, there is not a fee limitation, and it consumes less gas.\r
     * @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
     * @param feeAddress The fee recipient.\r
     * @dev Uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.\r
     *      Make sure this contract has enough allowance to spend the payer's token.\r
     *      Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.\r
     */\r
    function _batchERC20Payments(\r
        RequestDetail[] calldata requestDetails,\r
        address[][] calldata pathsToUSD,\r
        uint256 batchFeeAmountUSD,\r
        address feeAddress\r
    ) internal returns (uint256) {\r
        uint256 amountAndFee = 0;\r
        uint256 batchFeeAmount = 0;\r
        for (uint256 i = 0; i < requestDetails.length; i++) {\r
            amountAndFee +=\r
                requestDetails[i].requestAmount +\r
                requestDetails[i].feeAmount;\r
            batchFeeAmount += requestDetails[i].requestAmount;\r
        }\r
        batchFeeAmount = (batchFeeAmount * batchFee) / feeDenominator;\r
\r
        // batchFeeToPay and batchFeeAmountUSD are updated if needed\r
        (batchFeeAmount, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
            batchFeeAmount,\r
            requestDetails[0].path[0],\r
            batchFeeAmountUSD,\r
            pathsToUSD\r
        );\r
\r
        IERC20 requestedToken = IERC20(requestDetails[0].path[0]);\r
\r
        transferToContract(\r
            requestedToken,\r
            amountAndFee,\r
            batchFeeAmount,\r
            address(paymentErc20Proxy)\r
        );\r
\r
        // Payer pays batch fee amount\r
        require(\r
            safeTransferFrom(\r
                requestDetails[0].path[0],\r
                feeAddress,\r
                batchFeeAmount\r
            ),\r
            "Batch fee transferFrom() failed"\r
        );\r
\r
        // Batch contract pays the requests using Erc20FeeProxy\r
        for (uint256 i = 0; i < requestDetails.length; i++) {\r
            RequestDetail calldata rD = requestDetails[i];\r
            paymentErc20Proxy.transferFromWithReferenceAndFee(\r
                rD.path[0],\r
                rD.recipient,\r
                rD.requestAmount,\r
                rD.paymentReference,\r
                rD.feeAmount,\r
                feeAddress\r
            );\r
        }\r
\r
        return batchFeeAmountUSD;\r
    }\r
\r
    /**\r
     * @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts, with multiple tokens.\r
     * @param requestDetails List of ERC20 requests to pay.\r
     * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
     *                   Without paths, there is not a fee limitation, and it consumes less gas.\r
     * @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
     * @param feeAddress The fee recipient.\r
     * @dev It uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.\r
     *      Make sure this contract has enough allowance to spend the payer's token.\r
     *      Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.\r
     */\r
    function _batchMultiERC20Payments(\r
        RequestDetail[] calldata requestDetails,\r
        address[][] calldata pathsToUSD,\r
        uint256 batchFeeAmountUSD,\r
        address feeAddress\r
    ) internal returns (uint256) {\r
        Token[] memory uTokens = getUTokens(requestDetails);\r
\r
        // The payer transfers tokens to the batch contract and pays batch fee\r
        for (\r
            uint256 i = 0;\r
            i < uTokens.length && uTokens[i].amountAndFee > 0;\r
            i++\r
        ) {\r
            uTokens[i].batchFeeAmount =\r
                (uTokens[i].batchFeeAmount * batchFee) /\r
                feeDenominator;\r
            IERC20 requestedToken = IERC20(uTokens[i].tokenAddress);\r
            transferToContract(\r
                requestedToken,\r
                uTokens[i].amountAndFee,\r
                uTokens[i].batchFeeAmount,\r
                address(paymentErc20Proxy)\r
            );\r
\r
            // Payer pays batch fee amount\r
\r
            uint256 batchFeeToPay = uTokens[i].batchFeeAmount;\r
\r
            (batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(\r
                batchFeeToPay,\r
                uTokens[i].tokenAddress,\r
                batchFeeAmountUSD,\r
                pathsToUSD\r
            );\r
\r
            require(\r
                safeTransferFrom(\r
                    uTokens[i].tokenAddress,\r
                    feeAddress,\r
                    batchFeeToPay\r
                ),\r
                "Batch fee transferFrom() failed"\r
            );\r
        }\r
\r
        // Batch contract pays the requests using Erc20FeeProxy\r
        for (uint256 i = 0; i < requestDetails.length; i++) {\r
            RequestDetail calldata rD = requestDetails[i];\r
            paymentErc20Proxy.transferFromWithReferenceAndFee(\r
                rD.path[0],\r
                rD.recipient,\r
                rD.requestAmount,\r
                rD.paymentReference,\r
                rD.feeAmount,\r
                feeAddress\r
            );\r
        }\r
        return batchFeeAmountUSD;\r
    }\r
\r
    /*\r
     * Helper functions\r
     */\r
\r
    /**\r
     * Top up the contract with enough `requestedToken` to pay `amountAndFee`.\r
     * The contract is NOT topped-up for `batchFeeAmount`.\r
     *\r
     * It also performs a few checks:\r
     * - checks that the batch contract has enough allowance from the payer\r
     * - checks that the payer has enough funds, including batch fees\r
     * - increases the allowance of the contract to use the payment proxy if needed\r
     *\r
     * @param requestedToken The token to pay\r
     * @param amountAndFee The amount and the fee for a token to pay\r
     * @param batchFeeAmount The batch fee amount for a token to pay\r
     * @param paymentProxyAddress The payment proxy address used to pay\r
     */\r
    function transferToContract(\r
        IERC20 requestedToken,\r
        uint256 amountAndFee,\r
        uint256 batchFeeAmount,\r
        address paymentProxyAddress\r
    ) internal {\r
        // Check proxy's allowance from user\r
        require(\r
            requestedToken.allowance(msg.sender, address(this)) >= amountAndFee,\r
            "Insufficient allowance for batch to pay"\r
        );\r
        // Check user's funds to pay amounts, it is an approximation for conversion payment\r
        require(\r
            requestedToken.balanceOf(msg.sender) >=\r
                amountAndFee + batchFeeAmount,\r
            "Not enough funds, including fees"\r
        );\r
\r
        // Transfer the amount and fees (no batch fees) required for the token on the batch contract\r
        require(\r
            safeTransferFrom(\r
                address(requestedToken),\r
                address(this),\r
                amountAndFee\r
            ),\r
            "payment transferFrom() failed"\r
        );\r
\r
        // Batch contract approves Erc20ConversionProxy to spend the token\r
        if (\r
            requestedToken.allowance(address(this), paymentProxyAddress) <\r
            amountAndFee\r
        ) {\r
            approvePaymentProxyToSpend(\r
                address(requestedToken),\r
                paymentProxyAddress\r
            );\r
        }\r
    }\r
\r
    /**\r
     * It create a list of unique tokens used and the amounts associated.\r
     * It only considers tokens having: requestAmount + feeAmount > 0.\r
     * Regarding ERC20 no conversion payments:\r
     *   batchFeeAmount is the sum of requestAmount and feeAmount.\r
     *   Out of the function, batch fee rate is applied\r
     * @param requestDetails List of requests to pay.\r
     */\r
    function getUTokens(\r
        RequestDetail[] calldata requestDetails\r
    ) internal pure returns (Token[] memory uTokens) {\r
        // A list of unique tokens, with the sum of maxToSpend by token\r
        uTokens = new Token[](requestDetails.length);\r
        for (uint256 i = 0; i < requestDetails.length; i++) {\r
            for (uint256 k = 0; k < requestDetails.length; k++) {\r
                RequestDetail calldata rD = requestDetails[i];\r
                // If the token is already in the existing uTokens list\r
                if (uTokens[k].tokenAddress == rD.path[rD.path.length - 1]) {\r
                    if (rD.path.length > 1) {\r
                        uTokens[k].amountAndFee += rD.maxToSpend;\r
                    } else {\r
                        // It is not a conversion payment\r
                        uTokens[k].amountAndFee +=\r
                            rD.requestAmount +\r
                            rD.feeAmount;\r
                        uTokens[k].batchFeeAmount += rD.requestAmount;\r
                    }\r
                    break;\r
                }\r
                // If the token is not in the list (amountAndFee = 0)\r
                else if (\r
                    uTokens[k].amountAndFee == 0 &&\r
                    (rD.maxToSpend > 0 || rD.requestAmount + rD.feeAmount > 0)\r
                ) {\r
                    uTokens[k].tokenAddress = rD.path[rD.path.length - 1];\r
\r
                    if (rD.path.length > 1) {\r
                        // amountAndFee is used to store _maxToSpend, useful to send enough tokens to this contract\r
                        uTokens[k].amountAndFee = rD.maxToSpend;\r
                    } else {\r
                        // It is not a conversion payment\r
                        uTokens[k].amountAndFee =\r
                            rD.requestAmount +\r
                            rD.feeAmount;\r
                        uTokens[k].batchFeeAmount = rD.requestAmount;\r
                    }\r
                    break;\r
                }\r
            }\r
        }\r
    }\r
\r
    /**\r
     * Calculate the batch fee amount to pay, using the USD fee limitation.\r
     * Without pathsToUSD or a wrong one, the fee limitation is not applied.\r
     * @param batchFeeToPay The amount of batch fee to pay\r
     * @param tokenAddress The address of the token\r
     * @param batchFeeAmountUSD The batch fee amount in USD already paid.\r
     * @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.\r
     *                   Without paths, there is not a fee limitation, and it consumes less gas.\r
     */\r
    function calculateBatchFeeToPay(\r
        uint256 batchFeeToPay,\r
        address tokenAddress,\r
        uint256 batchFeeAmountUSD,\r
        address[][] memory pathsToUSD\r
    ) internal view returns (uint256, uint256) {\r
        // Fees are not limited if there is no pathsToUSD\r
        // Excepted if batchFeeAmountUSD is already >= batchFeeAmountUSDLimit\r
        if (\r
            pathsToUSD.length == 0 && batchFeeAmountUSD < batchFeeAmountUSDLimit\r
        ) {\r
            return (batchFeeToPay, batchFeeAmountUSD);\r
        }\r
\r
        // Apply the fee limit and calculate if needed batchFeeToPay\r
        if (batchFeeAmountUSD < batchFeeAmountUSDLimit) {\r
            for (uint256 i = 0; i < pathsToUSD.length; i++) {\r
                // Check if the pathToUSD is right\r
                if (\r
                    pathsToUSD[i][0] == tokenAddress &&\r
                    pathsToUSD[i][pathsToUSD[i].length - 1] == USDAddress\r
                ) {\r
                    (uint256 conversionUSD, ) = chainlinkConversionPath\r
                        .getConversion(batchFeeToPay, pathsToUSD[i]);\r
                    // Calculate the batch fee to pay, taking care of the batchFeeAmountUSDLimit\r
                    uint256 conversionToPayUSD = conversionUSD;\r
                    if (\r
                        batchFeeAmountUSD + conversionToPayUSD >\r
                        batchFeeAmountUSDLimit\r
                    ) {\r
                        conversionToPayUSD =\r
                            batchFeeAmountUSDLimit -\r
                            batchFeeAmountUSD;\r
                        batchFeeToPay =\r
                            (batchFeeToPay * conversionToPayUSD) /\r
                            conversionUSD;\r
                    }\r
                    batchFeeAmountUSD += conversionToPayUSD;\r
                    // Add only once the fees\r
                    break;\r
                }\r
            }\r
        } else {\r
            batchFeeToPay = 0;\r
        }\r
        return (batchFeeToPay, batchFeeAmountUSD);\r
    }\r
\r
    /**\r
     * @notice Authorizes the proxy to spend a new request currency (ERC20).\r
     * @param _erc20Address Address of an ERC20 used as the request currency.\r
     * @param _paymentErc20Proxy Address of the proxy.\r
     */\r
    function approvePaymentProxyToSpend(\r
        address _erc20Address,\r
        address _paymentErc20Proxy\r
    ) internal {\r
        IERC20 erc20 = IERC20(_erc20Address);\r
        uint256 max = 2 ** 256 - 1;\r
        erc20.safeApprove(address(_paymentErc20Proxy), max);\r
    }\r
\r
    /**\r
     * @notice Call transferFrom ERC20 function and validates the return data of a ERC20 contract call.\r
     * @dev This is necessary because of non-standard ERC20 tokens that don't have a return value.\r
     * @return result The return value of the ERC20 call, returning true for non-standard tokens\r
     */\r
    function safeTransferFrom(\r
        address _tokenAddress,\r
        address _to,\r
        uint256 _amount\r
    ) internal returns (bool result) {\r
        /* solium-disable security/no-inline-assembly */\r
        // check if the address is a contract\r
        assembly {\r
            if iszero(extcodesize(_tokenAddress)) {\r
                revert(0, 0)\r
            }\r
        }\r
\r
        // solium-disable-next-line security/no-low-level-calls\r
        (bool success, ) = _tokenAddress.call(\r
            abi.encodeWithSignature(\r
                "transferFrom(address,address,uint256)",\r
                msg.sender,\r
                _to,\r
                _amount\r
            )\r
        );\r
\r
        assembly {\r
            switch returndatasize()\r
            case 0 {\r
                // Not a standard erc20\r
                result := 1\r
            }\r
            case 32 {\r
                // Standard erc20\r
                returndatacopy(0, 0, 32)\r
                result := mload(0)\r
            }\r
            default {\r
                // Anything else, should revert for safety\r
                revert(0, 0)\r
            }\r
        }\r
\r
        require(success, "transferFrom() has been reverted");\r
\r
        /* solium-enable security/no-inline-assembly */\r
        return result;\r
    }\r
\r
    /*\r
     * Admin functions to edit the proxies address and fees\r
     */\r
\r
    /**\r
     * @notice Fees added when using Erc20/Native batch function

Tags:
ERC20, Multisig, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x3cf6a3b3ed708d829bc87f47e5175779e7abc03f|verified:true|block:23718691|tx:0xec92bcdc67c2358356eea0be4a41c8dd3b5f1f56b875d5b4c2f14f9d12a127dd|first_check:1762175194

Submitted on: 2025-11-03 14:06:35

Comments

Log in to comment.

No comments yet.