文件 1 的 15:BatchConversionPayments.sol
pragma solidity ^0.8.4;
import './interfaces/IERC20ConversionProxy.sol';
import './interfaces/IEthConversionProxy.sol';
import './BatchNoConversionPayments.sol';
contract BatchConversionPayments is BatchNoConversionPayments {
using SafeERC20 for IERC20;
IERC20ConversionProxy public paymentErc20ConversionProxy;
IEthConversionProxy public paymentNativeConversionProxy;
bool private payerAuthorized = false;
struct MetaDetail {
uint256 paymentNetworkId;
RequestDetail[] requestDetails;
}
constructor(
address _paymentErc20Proxy,
address _paymentNativeProxy,
address _paymentErc20ConversionProxy,
address _paymentNativeConversionFeeProxy,
address _chainlinkConversionPath,
address _owner
)
BatchNoConversionPayments(
_paymentErc20Proxy,
_paymentNativeProxy,
_chainlinkConversionPath,
_owner
)
{
paymentErc20ConversionProxy = IERC20ConversionProxy(_paymentErc20ConversionProxy);
paymentNativeConversionProxy = IEthConversionProxy(_paymentNativeConversionFeeProxy);
}
receive() external payable override {
require(payerAuthorized || msg.value == 0, 'Non-payable');
}
function batchPayments(
MetaDetail[] calldata metaDetails,
address[][] calldata pathsToUSD,
address feeAddress
) external payable {
require(metaDetails.length < 6, 'more than 5 metaDetails');
uint256 batchFeeAmountUSD = 0;
for (uint256 i = 0; i < metaDetails.length; i++) {
MetaDetail calldata metaDetail = metaDetails[i];
if (metaDetail.paymentNetworkId == 0) {
batchFeeAmountUSD += _batchMultiERC20ConversionPayments(
metaDetail.requestDetails,
batchFeeAmountUSD,
pathsToUSD,
feeAddress
);
} else if (metaDetail.paymentNetworkId == 1) {
batchFeeAmountUSD += _batchERC20Payments(
metaDetail.requestDetails,
pathsToUSD,
batchFeeAmountUSD,
payable(feeAddress)
);
} else if (metaDetail.paymentNetworkId == 2) {
batchFeeAmountUSD += _batchMultiERC20Payments(
metaDetail.requestDetails,
pathsToUSD,
batchFeeAmountUSD,
feeAddress
);
} else if (metaDetail.paymentNetworkId == 3) {
if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {
transferBackRemainingNativeTokens = false;
}
batchFeeAmountUSD += _batchNativePayments(
metaDetail.requestDetails,
pathsToUSD.length == 0,
batchFeeAmountUSD,
payable(feeAddress)
);
if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {
transferBackRemainingNativeTokens = true;
}
} else if (metaDetail.paymentNetworkId == 4) {
batchFeeAmountUSD += _batchNativeConversionPayments(
metaDetail.requestDetails,
pathsToUSD.length == 0,
batchFeeAmountUSD,
payable(feeAddress)
);
} else {
revert('Wrong paymentNetworkId');
}
}
}
function batchMultiERC20ConversionPayments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
address feeAddress
) public returns (uint256) {
return _batchMultiERC20ConversionPayments(requestDetails, 0, pathsToUSD, feeAddress);
}
function batchNativeConversionPayments(
RequestDetail[] calldata requestDetails,
bool skipFeeUSDLimit,
address payable feeAddress
) public payable returns (uint256) {
return _batchNativeConversionPayments(requestDetails, skipFeeUSDLimit, 0, feeAddress);
}
function _batchMultiERC20ConversionPayments(
RequestDetail[] calldata requestDetails,
uint256 batchFeeAmountUSD,
address[][] calldata pathsToUSD,
address feeAddress
) private returns (uint256) {
Token[] memory uTokens = getUTokens(requestDetails);
IERC20 requestedToken;
for (uint256 k = 0; k < uTokens.length && uTokens[k].amountAndFee > 0; k++) {
uTokens[k].batchFeeAmount = (uTokens[k].amountAndFee * batchFee) / feeDenominator;
requestedToken = IERC20(uTokens[k].tokenAddress);
transferToContract(
requestedToken,
uTokens[k].amountAndFee,
uTokens[k].batchFeeAmount,
address(paymentErc20ConversionProxy)
);
}
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
paymentErc20ConversionProxy.transferFromWithReferenceAndFee(
rD.recipient,
rD.requestAmount,
rD.path,
rD.paymentReference,
rD.feeAmount,
feeAddress,
rD.maxToSpend,
rD.maxRateTimespan
);
}
for (uint256 k = 0; k < uTokens.length && uTokens[k].amountAndFee > 0; k++) {
requestedToken = IERC20(uTokens[k].tokenAddress);
uint256 excessAmount = requestedToken.balanceOf(address(this));
if (excessAmount > 0) {
requestedToken.safeTransfer(msg.sender, excessAmount);
}
uint256 batchFeeToPay = ((uTokens[k].amountAndFee - excessAmount) * batchFee) /
feeDenominator;
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(
batchFeeToPay,
uTokens[k].tokenAddress,
batchFeeAmountUSD,
pathsToUSD
);
require(
safeTransferFrom(uTokens[k].tokenAddress, feeAddress, batchFeeToPay),
'Batch fee transferFrom() failed'
);
}
return batchFeeAmountUSD;
}
function _batchNativeConversionPayments(
RequestDetail[] calldata requestDetails,
bool skipFeeUSDLimit,
uint256 batchFeeAmountUSD,
address payable feeAddress
) private returns (uint256) {
uint256 contractBalance = address(this).balance;
payerAuthorized = true;
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
paymentNativeConversionProxy.transferWithReferenceAndFee{value: address(this).balance}(
payable(rD.recipient),
rD.requestAmount,
rD.path,
rD.paymentReference,
rD.feeAmount,
feeAddress,
rD.maxRateTimespan
);
}
uint256 batchFeeToPay = (((contractBalance - address(this).balance)) * batchFee) /
feeDenominator;
if (skipFeeUSDLimit == false) {
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(
batchFeeToPay,
pathsNativeToUSD[0][0],
batchFeeAmountUSD,
pathsNativeToUSD
);
}
require(address(this).balance >= batchFeeToPay, 'Not enough funds for batch conversion fees');
feeAddress.transfer(batchFeeToPay);
(bool sendBackSuccess, ) = payable(msg.sender).call{value: address(this).balance}('');
require(sendBackSuccess, 'Could not send remaining funds to the payer');
payerAuthorized = false;
return batchFeeAmountUSD;
}
function setPaymentErc20ConversionProxy(address _paymentErc20ConversionProxy) external onlyOwner {
paymentErc20ConversionProxy = IERC20ConversionProxy(_paymentErc20ConversionProxy);
}
function setPaymentNativeConversionProxy(address _paymentNativeConversionProxy)
external
onlyOwner
{
paymentNativeConversionProxy = IEthConversionProxy(_paymentNativeConversionProxy);
}
}
文件 2 的 15:BatchNoConversionPayments.sol
pragma solidity ^0.8.4;
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import './lib/SafeERC20.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
import './interfaces/ERC20FeeProxy.sol';
import './interfaces/EthereumFeeProxy.sol';
import './ChainlinkConversionPath.sol';
contract BatchNoConversionPayments is Ownable {
using SafeERC20 for IERC20;
IERC20FeeProxy public paymentErc20Proxy;
IEthereumFeeProxy public paymentNativeProxy;
ChainlinkConversionPath public chainlinkConversionPath;
uint16 public batchFee;
uint16 internal feeDenominator = 10000;
uint64 public batchFeeAmountUSDLimit;
bool internal transferBackRemainingNativeTokens = true;
address public USDAddress;
address public NativeAddress;
address[][] public pathsNativeToUSD;
struct Token {
address tokenAddress;
uint256 amountAndFee;
uint256 batchFeeAmount;
}
struct RequestDetail {
address recipient;
uint256 requestAmount;
address[] path;
bytes paymentReference;
uint256 feeAmount;
uint256 maxToSpend;
uint256 maxRateTimespan;
}
constructor(
address _paymentErc20Proxy,
address _paymentNativeProxy,
address _chainlinkConversionPath,
address _owner
) {
paymentErc20Proxy = IERC20FeeProxy(_paymentErc20Proxy);
paymentNativeProxy = IEthereumFeeProxy(_paymentNativeProxy);
chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPath);
transferOwnership(_owner);
batchFee = 0;
}
receive() external payable virtual {
require(msg.value == 0, 'Non-payable');
}
function batchNativePayments(
RequestDetail[] calldata requestDetails,
bool skipFeeUSDLimit,
address payable feeAddress
) public payable returns (uint256) {
return _batchNativePayments(requestDetails, skipFeeUSDLimit, 0, payable(feeAddress));
}
function batchERC20Payments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
address feeAddress
) public returns (uint256) {
return _batchERC20Payments(requestDetails, pathsToUSD, 0, feeAddress);
}
function batchMultiERC20Payments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
address feeAddress
) public returns (uint256) {
return _batchMultiERC20Payments(requestDetails, pathsToUSD, 0, feeAddress);
}
function _batchNativePayments(
RequestDetail[] calldata requestDetails,
bool skipFeeUSDLimit,
uint256 batchFeeAmountUSD,
address payable feeAddress
) internal returns (uint256) {
uint256 amount = 0;
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
require(address(this).balance >= rD.requestAmount + rD.feeAmount, 'Not enough funds');
amount += rD.requestAmount;
paymentNativeProxy.transferWithReferenceAndFee{value: rD.requestAmount + rD.feeAmount}(
payable(rD.recipient),
rD.paymentReference,
rD.feeAmount,
payable(feeAddress)
);
}
amount = (amount * batchFee) / feeDenominator;
if (skipFeeUSDLimit == false) {
(amount, batchFeeAmountUSD) = calculateBatchFeeToPay(
amount,
pathsNativeToUSD[0][0],
batchFeeAmountUSD,
pathsNativeToUSD
);
}
require(address(this).balance >= amount, 'Not enough funds for batch fee');
feeAddress.transfer(amount);
if (transferBackRemainingNativeTokens && address(this).balance > 0) {
(bool sendBackSuccess, ) = payable(msg.sender).call{value: address(this).balance}('');
require(sendBackSuccess, 'Could not send remaining funds to the payer');
}
return batchFeeAmountUSD;
}
function _batchERC20Payments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
uint256 batchFeeAmountUSD,
address feeAddress
) internal returns (uint256) {
uint256 amountAndFee = 0;
uint256 batchFeeAmount = 0;
for (uint256 i = 0; i < requestDetails.length; i++) {
amountAndFee += requestDetails[i].requestAmount + requestDetails[i].feeAmount;
batchFeeAmount += requestDetails[i].requestAmount;
}
batchFeeAmount = (batchFeeAmount * batchFee) / feeDenominator;
(batchFeeAmount, batchFeeAmountUSD) = calculateBatchFeeToPay(
batchFeeAmount,
requestDetails[0].path[0],
batchFeeAmountUSD,
pathsToUSD
);
IERC20 requestedToken = IERC20(requestDetails[0].path[0]);
transferToContract(requestedToken, amountAndFee, batchFeeAmount, address(paymentErc20Proxy));
require(
safeTransferFrom(requestDetails[0].path[0], feeAddress, batchFeeAmount),
'Batch fee transferFrom() failed'
);
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
paymentErc20Proxy.transferFromWithReferenceAndFee(
rD.path[0],
rD.recipient,
rD.requestAmount,
rD.paymentReference,
rD.feeAmount,
feeAddress
);
}
return batchFeeAmountUSD;
}
function _batchMultiERC20Payments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
uint256 batchFeeAmountUSD,
address feeAddress
) internal returns (uint256) {
Token[] memory uTokens = getUTokens(requestDetails);
for (uint256 i = 0; i < uTokens.length && uTokens[i].amountAndFee > 0; i++) {
uTokens[i].batchFeeAmount = (uTokens[i].batchFeeAmount * batchFee) / feeDenominator;
IERC20 requestedToken = IERC20(uTokens[i].tokenAddress);
transferToContract(
requestedToken,
uTokens[i].amountAndFee,
uTokens[i].batchFeeAmount,
address(paymentErc20Proxy)
);
uint256 batchFeeToPay = uTokens[i].batchFeeAmount;
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(
batchFeeToPay,
uTokens[i].tokenAddress,
batchFeeAmountUSD,
pathsToUSD
);
require(
safeTransferFrom(uTokens[i].tokenAddress, feeAddress, batchFeeToPay),
'Batch fee transferFrom() failed'
);
}
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
paymentErc20Proxy.transferFromWithReferenceAndFee(
rD.path[0],
rD.recipient,
rD.requestAmount,
rD.paymentReference,
rD.feeAmount,
feeAddress
);
}
return batchFeeAmountUSD;
}
function transferToContract(
IERC20 requestedToken,
uint256 amountAndFee,
uint256 batchFeeAmount,
address paymentProxyAddress
) internal {
require(
requestedToken.allowance(msg.sender, address(this)) >= amountAndFee,
'Insufficient allowance for batch to pay'
);
require(
requestedToken.balanceOf(msg.sender) >= amountAndFee + batchFeeAmount,
'Not enough funds, including fees'
);
require(
safeTransferFrom(address(requestedToken), address(this), amountAndFee),
'payment transferFrom() failed'
);
if (requestedToken.allowance(address(this), paymentProxyAddress) < amountAndFee) {
approvePaymentProxyToSpend(address(requestedToken), paymentProxyAddress);
}
}
function getUTokens(RequestDetail[] calldata requestDetails)
internal
pure
returns (Token[] memory uTokens)
{
uTokens = new Token[](requestDetails.length);
for (uint256 i = 0; i < requestDetails.length; i++) {
for (uint256 k = 0; k < requestDetails.length; k++) {
RequestDetail calldata rD = requestDetails[i];
if (uTokens[k].tokenAddress == rD.path[rD.path.length - 1]) {
if (rD.path.length > 1) {
uTokens[k].amountAndFee += rD.maxToSpend;
} else {
uTokens[k].amountAndFee += rD.requestAmount + rD.feeAmount;
uTokens[k].batchFeeAmount += rD.requestAmount;
}
break;
}
else if (
uTokens[k].amountAndFee == 0 && (rD.maxToSpend > 0 || rD.requestAmount + rD.feeAmount > 0)
) {
uTokens[k].tokenAddress = rD.path[rD.path.length - 1];
if (rD.path.length > 1) {
uTokens[k].amountAndFee = rD.maxToSpend;
} else {
uTokens[k].amountAndFee = rD.requestAmount + rD.feeAmount;
uTokens[k].batchFeeAmount = rD.requestAmount;
}
break;
}
}
}
}
function calculateBatchFeeToPay(
uint256 batchFeeToPay,
address tokenAddress,
uint256 batchFeeAmountUSD,
address[][] memory pathsToUSD
) internal view returns (uint256, uint256) {
if (pathsToUSD.length == 0 && batchFeeAmountUSD < batchFeeAmountUSDLimit) {
return (batchFeeToPay, batchFeeAmountUSD);
}
if (batchFeeAmountUSD < batchFeeAmountUSDLimit) {
for (uint256 i = 0; i < pathsToUSD.length; i++) {
if (
pathsToUSD[i][0] == tokenAddress && pathsToUSD[i][pathsToUSD[i].length - 1] == USDAddress
) {
(uint256 conversionUSD, ) = chainlinkConversionPath.getConversion(
batchFeeToPay,
pathsToUSD[i]
);
uint256 conversionToPayUSD = conversionUSD;
if (batchFeeAmountUSD + conversionToPayUSD > batchFeeAmountUSDLimit) {
conversionToPayUSD = batchFeeAmountUSDLimit - batchFeeAmountUSD;
batchFeeToPay = (batchFeeToPay * conversionToPayUSD) / conversionUSD;
}
batchFeeAmountUSD += conversionToPayUSD;
break;
}
}
} else {
batchFeeToPay = 0;
}
return (batchFeeToPay, batchFeeAmountUSD);
}
function approvePaymentProxyToSpend(address _erc20Address, address _paymentErc20Proxy) internal {
IERC20 erc20 = IERC20(_erc20Address);
uint256 max = 2**256 - 1;
erc20.safeApprove(address(_paymentErc20Proxy), max);
}
function safeTransferFrom(
address _tokenAddress,
address _to,
uint256 _amount
) internal returns (bool result) {
assembly {
if iszero(extcodesize(_tokenAddress)) {
revert(0, 0)
}
}
(bool success, ) = _tokenAddress.call(
abi.encodeWithSignature('transferFrom(address,address,uint256)', msg.sender, _to, _amount)
);
assembly {
switch returndatasize()
case 0 {
result := 1
}
case 32 {
returndatacopy(0, 0, 32)
result := mload(0)
}
default {
revert(0, 0)
}
}
require(success, 'transferFrom() has been reverted');
return result;
}
function setBatchFee(uint16 _batchFee) external onlyOwner {
require(_batchFee <= 200, 'The batch fee value is too high: > 2%');
batchFee = _batchFee;
}
function setPaymentErc20Proxy(address _paymentErc20Proxy) external onlyOwner {
paymentErc20Proxy = IERC20FeeProxy(_paymentErc20Proxy);
}
function setPaymentNativeProxy(address _paymentNativeProxy) external onlyOwner {
paymentNativeProxy = IEthereumFeeProxy(_paymentNativeProxy);
}
function setChainlinkConversionPath(address _chainlinkConversionPath) external onlyOwner {
chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPath);
}
function setNativeAndUSDAddress(address _NativeAddress, address _USDAddress) external onlyOwner {
NativeAddress = _NativeAddress;
USDAddress = _USDAddress;
pathsNativeToUSD = [[NativeAddress, USDAddress]];
}
function setBatchFeeAmountUSDLimit(uint64 _batchFeeAmountUSDLimit) external onlyOwner {
batchFeeAmountUSDLimit = _batchFeeAmountUSDLimit;
}
}
文件 3 的 15:ChainlinkConversionPath.sol
pragma solidity ^0.8.0;
import './legacy_openzeppelin/contracts/access/roles/WhitelistAdminRole.sol';
interface ERC20fraction {
function decimals() external view returns (uint8);
}
interface AggregatorFraction {
function decimals() external view returns (uint8);
function latestAnswer() external view returns (int256);
function latestTimestamp() external view returns (uint256);
}
contract ChainlinkConversionPath is WhitelistAdminRole {
uint256 constant PRECISION = 1e18;
uint256 constant NATIVE_TOKEN_DECIMALS = 18;
uint256 constant FIAT_DECIMALS = 8;
address public nativeTokenHash;
constructor(address _nativeTokenHash) {
nativeTokenHash = _nativeTokenHash;
}
mapping(address => mapping(address => address)) public allAggregators;
event AggregatorUpdated(address _input, address _output, address _aggregator);
function updateAggregator(
address _input,
address _output,
address _aggregator
) external onlyWhitelistAdmin {
allAggregators[_input][_output] = _aggregator;
emit AggregatorUpdated(_input, _output, _aggregator);
}
function updateAggregatorsList(
address[] calldata _inputs,
address[] calldata _outputs,
address[] calldata _aggregators
) external onlyWhitelistAdmin {
require(_inputs.length == _outputs.length, 'arrays must have the same length');
require(_inputs.length == _aggregators.length, 'arrays must have the same length');
for (uint256 i; i < _inputs.length; i++) {
allAggregators[_inputs[i]][_outputs[i]] = _aggregators[i];
emit AggregatorUpdated(_inputs[i], _outputs[i], _aggregators[i]);
}
}
function getConversion(uint256 _amountIn, address[] calldata _path)
external
view
returns (uint256 result, uint256 oldestRateTimestamp)
{
(uint256 rate, uint256 timestamp, uint256 decimals) = getRate(_path);
result = (_amountIn * rate) / decimals;
oldestRateTimestamp = timestamp;
}
function getRate(address[] memory _path)
public
view
returns (
uint256 rate,
uint256 oldestRateTimestamp,
uint256 decimals
)
{
rate = PRECISION;
decimals = PRECISION;
oldestRateTimestamp = block.timestamp;
for (uint256 i; i < _path.length - 1; i++) {
(
AggregatorFraction aggregator,
bool reverseAggregator,
uint256 decimalsInput,
uint256 decimalsOutput
) = getAggregatorAndDecimals(_path[i], _path[i + 1]);
uint256 currentTimestamp = aggregator.latestTimestamp();
if (currentTimestamp < oldestRateTimestamp) {
oldestRateTimestamp = currentTimestamp;
}
uint256 currentRate = uint256(aggregator.latestAnswer());
uint256 decimalsAggregator = uint256(aggregator.decimals());
if (decimalsAggregator > decimalsInput) {
rate = rate * (10**(decimalsAggregator - decimalsInput));
}
if (decimalsAggregator < decimalsOutput) {
rate = rate * (10**(decimalsOutput - decimalsAggregator));
}
if (reverseAggregator) {
rate = (rate * (10**decimalsAggregator)) / currentRate;
} else {
rate = (rate * currentRate) / (10**decimalsAggregator);
}
if (decimalsAggregator < decimalsInput) {
rate = rate / (10**(decimalsInput - decimalsAggregator));
}
if (decimalsAggregator > decimalsOutput) {
rate = rate / (10**(decimalsAggregator - decimalsOutput));
}
}
}
function getAggregatorAndDecimals(address _input, address _output)
private
view
returns (
AggregatorFraction aggregator,
bool reverseAggregator,
uint256 decimalsInput,
uint256 decimalsOutput
)
{
aggregator = AggregatorFraction(allAggregators[_input][_output]);
reverseAggregator = false;
if (address(aggregator) == address(0x00)) {
aggregator = AggregatorFraction(allAggregators[_output][_input]);
reverseAggregator = true;
}
require(address(aggregator) != address(0x00), 'No aggregator found');
decimalsInput = getDecimals(_input);
decimalsOutput = getDecimals(_output);
}
function getDecimals(address _addr) private view returns (uint256 decimals) {
decimals = FIAT_DECIMALS;
if (_addr == nativeTokenHash) {
decimals = NATIVE_TOKEN_DECIMALS;
} else if (isContract(_addr)) {
decimals = ERC20fraction(_addr).decimals();
}
}
function isContract(address _addr) private view returns (bool) {
uint32 size;
assembly {
size := extcodesize(_addr)
}
return (size > 0);
}
}
文件 4 的 15:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 5 的 15:ERC20.sol
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
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;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function decimals() public view virtual override returns (uint8) {
return 18;
}
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][_msgSender()];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, _msgSender(), currentAllowance - amount);
}
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
uint256 currentAllowance = _allowances[_msgSender()][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(_msgSender(), spender, currentAllowance - subtractedValue);
}
return true;
}
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
_afterTokenTransfer(sender, recipient, amount);
}
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;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
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;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
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);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
文件 6 的 15:ERC20FeeProxy.sol
pragma solidity ^0.8.0;
interface IERC20FeeProxy {
event TransferWithReferenceAndFee(
address tokenAddress,
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
function transferFromWithReferenceAndFee(
address _tokenAddress,
address _to,
uint256 _amount,
bytes calldata _paymentReference,
uint256 _feeAmount,
address _feeAddress
) external;
}
文件 7 的 15:EthereumFeeProxy.sol
pragma solidity ^0.8.0;
interface IEthereumFeeProxy {
event TransferWithReferenceAndFee(
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
function transferWithReferenceAndFee(
address payable _to,
bytes calldata _paymentReference,
uint256 _feeAmount,
address payable _feeAddress
) external payable;
}
文件 8 的 15:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 9 的 15:IERC20ConversionProxy.sol
pragma solidity ^0.8.0;
interface IERC20ConversionProxy {
event TransferWithConversionAndReference(
uint256 amount,
address currency,
bytes indexed paymentReference,
uint256 feeAmount,
uint256 maxRateTimespan
);
event TransferWithReferenceAndFee(
address tokenAddress,
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
function transferFromWithReferenceAndFee(
address _to,
uint256 _requestAmount,
address[] calldata _path,
bytes calldata _paymentReference,
uint256 _feeAmount,
address _feeAddress,
uint256 _maxToSpend,
uint256 _maxRateTimespan
) external;
}
文件 10 的 15:IERC20Metadata.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
文件 11 的 15:IEthConversionProxy.sol
pragma solidity ^0.8.0;
interface IEthConversionProxy {
event TransferWithConversionAndReference(
uint256 amount,
address currency,
bytes indexed paymentReference,
uint256 feeAmount,
uint256 maxRateTimespan
);
event TransferWithReferenceAndFee(
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
function transferWithReferenceAndFee(
address _to,
uint256 _requestAmount,
address[] calldata _path,
bytes calldata _paymentReference,
uint256 _feeAmount,
address _feeAddress,
uint256 _maxRateTimespan
) external payable;
}
文件 12 的 15:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 13 的 15:Roles.sol
pragma solidity ^0.8.0;
library Roles {
struct Role {
mapping(address => bool) bearer;
}
function add(Role storage role, address account) internal {
require(!has(role, account), 'Roles: account already has role');
role.bearer[account] = true;
}
function remove(Role storage role, address account) internal {
require(has(role, account), 'Roles: account does not have role');
role.bearer[account] = false;
}
function has(Role storage role, address account) internal view returns (bool) {
require(account != address(0), 'Roles: account is the zero address');
return role.bearer[account];
}
}
文件 14 的 15:SafeERC20.sol
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
library SafeERC20 {
function safeTransferFrom(
IERC20 _token,
address _from,
address _to,
uint256 _amount
) internal returns (bool result) {
(bool success, bytes memory data) = address(_token).call(
abi.encodeWithSignature('transferFrom(address,address,uint256)', _from, _to, _amount)
);
return success && (data.length == 0 || abi.decode(data, (bool)));
}
function safeApprove(
IERC20 _token,
address _spender,
uint256 _amount
) internal returns (bool result) {
(bool success, bytes memory data) = address(_token).call(
abi.encodeWithSignature('approve(address,uint256)', _spender, _amount)
);
return success && (data.length == 0 || abi.decode(data, (bool)));
}
function safeTransfer(
IERC20 _token,
address _to,
uint256 _amount
) internal returns (bool result) {
(bool success, bytes memory data) = address(_token).call(
abi.encodeWithSignature('transfer(address,uint256)', _to, _amount)
);
return success && (data.length == 0 || abi.decode(data, (bool)));
}
}
文件 15 的 15:WhitelistAdminRole.sol
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/utils/Context.sol';
import '../Roles.sol';
abstract contract WhitelistAdminRole is Context {
using Roles for Roles.Role;
event WhitelistAdminAdded(address indexed account);
event WhitelistAdminRemoved(address indexed account);
Roles.Role private _whitelistAdmins;
constructor() {
_addWhitelistAdmin(_msgSender());
}
modifier onlyWhitelistAdmin() {
require(
isWhitelistAdmin(_msgSender()),
'WhitelistAdminRole: caller does not have the WhitelistAdmin role'
);
_;
}
function isWhitelistAdmin(address account) public view returns (bool) {
return _whitelistAdmins.has(account);
}
function addWhitelistAdmin(address account) public onlyWhitelistAdmin {
_addWhitelistAdmin(account);
}
function renounceWhitelistAdmin() public {
_removeWhitelistAdmin(_msgSender());
}
function _addWhitelistAdmin(address account) internal {
_whitelistAdmins.add(account);
emit WhitelistAdminAdded(account);
}
function _removeWhitelistAdmin(address account) internal {
_whitelistAdmins.remove(account);
emit WhitelistAdminRemoved(account);
}
}
{
"compilationTarget": {
"src/contracts/BatchConversionPayments.sol": "BatchConversionPayments"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_paymentErc20Proxy","type":"address"},{"internalType":"address","name":"_paymentNativeProxy","type":"address"},{"internalType":"address","name":"_paymentErc20ConversionProxy","type":"address"},{"internalType":"address","name":"_paymentNativeConversionFeeProxy","type":"address"},{"internalType":"address","name":"_chainlinkConversionPath","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"NativeAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"USDAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"requestAmount","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"bytes","name":"paymentReference","type":"bytes"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"maxToSpend","type":"uint256"},{"internalType":"uint256","name":"maxRateTimespan","type":"uint256"}],"internalType":"struct BatchNoConversionPayments.RequestDetail[]","name":"requestDetails","type":"tuple[]"},{"internalType":"address[][]","name":"pathsToUSD","type":"address[][]"},{"internalType":"address","name":"feeAddress","type":"address"}],"name":"batchERC20Payments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"batchFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"batchFeeAmountUSDLimit","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"requestAmount","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"bytes","name":"paymentReference","type":"bytes"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"maxToSpend","type":"uint256"},{"internalType":"uint256","name":"maxRateTimespan","type":"uint256"}],"internalType":"struct BatchNoConversionPayments.RequestDetail[]","name":"requestDetails","type":"tuple[]"},{"internalType":"address[][]","name":"pathsToUSD","type":"address[][]"},{"internalType":"address","name":"feeAddress","type":"address"}],"name":"batchMultiERC20ConversionPayments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"requestAmount","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"bytes","name":"paymentReference","type":"bytes"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"maxToSpend","type":"uint256"},{"internalType":"uint256","name":"maxRateTimespan","type":"uint256"}],"internalType":"struct BatchNoConversionPayments.RequestDetail[]","name":"requestDetails","type":"tuple[]"},{"internalType":"address[][]","name":"pathsToUSD","type":"address[][]"},{"internalType":"address","name":"feeAddress","type":"address"}],"name":"batchMultiERC20Payments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"requestAmount","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"bytes","name":"paymentReference","type":"bytes"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"maxToSpend","type":"uint256"},{"internalType":"uint256","name":"maxRateTimespan","type":"uint256"}],"internalType":"struct BatchNoConversionPayments.RequestDetail[]","name":"requestDetails","type":"tuple[]"},{"internalType":"bool","name":"skipFeeUSDLimit","type":"bool"},{"internalType":"address payable","name":"feeAddress","type":"address"}],"name":"batchNativeConversionPayments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"requestAmount","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"bytes","name":"paymentReference","type":"bytes"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"maxToSpend","type":"uint256"},{"internalType":"uint256","name":"maxRateTimespan","type":"uint256"}],"internalType":"struct BatchNoConversionPayments.RequestDetail[]","name":"requestDetails","type":"tuple[]"},{"internalType":"bool","name":"skipFeeUSDLimit","type":"bool"},{"internalType":"address payable","name":"feeAddress","type":"address"}],"name":"batchNativePayments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"paymentNetworkId","type":"uint256"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"requestAmount","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"bytes","name":"paymentReference","type":"bytes"},{"internalType":"uint256","name":"feeAmount","type":"uint256"},{"internalType":"uint256","name":"maxToSpend","type":"uint256"},{"internalType":"uint256","name":"maxRateTimespan","type":"uint256"}],"internalType":"struct BatchNoConversionPayments.RequestDetail[]","name":"requestDetails","type":"tuple[]"}],"internalType":"struct BatchConversionPayments.MetaDetail[]","name":"metaDetails","type":"tuple[]"},{"internalType":"address[][]","name":"pathsToUSD","type":"address[][]"},{"internalType":"address","name":"feeAddress","type":"address"}],"name":"batchPayments","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"chainlinkConversionPath","outputs":[{"internalType":"contract ChainlinkConversionPath","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"pathsNativeToUSD","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paymentErc20ConversionProxy","outputs":[{"internalType":"contract IERC20ConversionProxy","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paymentErc20Proxy","outputs":[{"internalType":"contract IERC20FeeProxy","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paymentNativeConversionProxy","outputs":[{"internalType":"contract IEthConversionProxy","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paymentNativeProxy","outputs":[{"internalType":"contract IEthereumFeeProxy","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_batchFee","type":"uint16"}],"name":"setBatchFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"_batchFeeAmountUSDLimit","type":"uint64"}],"name":"setBatchFeeAmountUSDLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_chainlinkConversionPath","type":"address"}],"name":"setChainlinkConversionPath","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_NativeAddress","type":"address"},{"internalType":"address","name":"_USDAddress","type":"address"}],"name":"setNativeAndUSDAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_paymentErc20ConversionProxy","type":"address"}],"name":"setPaymentErc20ConversionProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_paymentErc20Proxy","type":"address"}],"name":"setPaymentErc20Proxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_paymentNativeConversionProxy","type":"address"}],"name":"setPaymentNativeConversionProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_paymentNativeProxy","type":"address"}],"name":"setPaymentNativeProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]