Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 91
feat: Develop SingleRequestProxy Smart Contracts#1453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
e2567f4c624474917d472d5e472805430922bd349519b5e40c1d3a771376582c2f55eebc922e88aef2340769aa73bd9e38ed429aed9a4484a341ce110974b52cc82a0bee1f58aab67110c68eba796f2b90482f6db8183f385fdc139033066cd91ec2f944199c77c1eb41e43b501c28d2cfba1b97241e0c4abe9e990b413fc49375806e015ac778b17b45607ac58dfc09ee661a0141d22File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -20,6 +20,7 @@ export const create2ContractDeploymentList = [ | ||
| 'BatchConversionPayments', | ||
| 'ERC20EscrowToPay', | ||
| 'ERC20TransferableReceivable', | ||
| 'SingleRequestProxyFactory', | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ]; | ||
| /** | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.9; | ||
| import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; | ||
| import './interfaces/ERC20FeeProxy.sol'; | ||
| import './lib/SafeERC20.sol'; | ||
| /** | ||
| * @title ERC20SingleRequestProxy | ||
| * @notice This contract is used to send a single request to a payee with a fee sent to a third address for ERC20 | ||
| */ | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| contract ERC20SingleRequestProxy { | ||
| address public payee; | ||
| address public tokenAddress; | ||
| address public feeAddress; | ||
| uint256 public feeAmount; | ||
leoslr marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| bytes public paymentReference; | ||
| IERC20FeeProxy public erc20FeeProxy; | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| constructor( | ||
| address _payee, | ||
| address _tokenAddress, | ||
| address _feeAddress, | ||
| uint256 _feeAmount, | ||
| bytes memory _paymentReference, | ||
| address _erc20FeeProxy | ||
| ) { | ||
| payee = _payee; | ||
| tokenAddress = _tokenAddress; | ||
| feeAddress = _feeAddress; | ||
| feeAmount = _feeAmount; | ||
| paymentReference = _paymentReference; | ||
| erc20FeeProxy = IERC20FeeProxy(_erc20FeeProxy); | ||
| } | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| receive() external payable { | ||
| require(msg.value == 0, 'This function is only for triggering the transfer'); | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| _processPayment(); | ||
| } | ||
| function triggerERC20Payment() external { | ||
| _processPayment(); | ||
| } | ||
| function _processPayment() internal { | ||
| IERC20 token = IERC20(tokenAddress); | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| uint256 balance = token.balanceOf(address(this)); | ||
| uint256 paymentAmount = balance; | ||
| if (feeAmount > 0 && feeAddress != address(0)) { | ||
| require(balance > feeAmount, 'Insufficient balance to cover fee'); | ||
| paymentAmount = balance - feeAmount; | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| require(SafeERC20.safeApprove(token, address(erc20FeeProxy), balance), 'Approval failed'); | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| erc20FeeProxy.transferFromWithReferenceAndFee( | ||
| tokenAddress, | ||
| payee, | ||
| paymentAmount, | ||
| paymentReference, | ||
| feeAmount, | ||
| feeAddress | ||
| ); | ||
| } | ||
Comment on lines
+46
to
+65
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add Reentrancy Guard to The Consider implementing a reentrancy guard to secure this function. You can use a custom reentrancy guard similar to the one used in the Apply this diff to add a reentrancy guard: + bool private locked;
function _processPayment() internal {
+ require(!locked, "Reentrant call detected");+ locked = true;
IERC20 token = IERC20(tokenAddress);
uint256 balance = token.balanceOf(address(this));
uint256 paymentAmount = balance;
if (feeAmount > 0 && feeAddress != address(0)) {
require(balance > feeAmount, 'Insufficient balance to cover fee');
paymentAmount = balance - feeAmount;
}
SafeERC20.safeApprove(token, address(erc20FeeProxy), balance);
erc20FeeProxy.transferFromWithReferenceAndFee(
tokenAddress,
payee,
paymentAmount,
paymentReference,
feeAmount,
feeAddress
);
+ locked = false;
}
| ||
| /** | ||
| * @notice Rescues any trapped funds by sending them to the payee | ||
| * @dev Can be called by anyone, but funds are always sent to the payee | ||
| */ | ||
| function rescueERC20Funds(address _tokenAddress) external { | ||
| require(_tokenAddress != address(0), 'Invalid token address'); | ||
| IERC20 token = IERC20(_tokenAddress); | ||
| uint256 balance = token.balanceOf(address(this)); | ||
| require(balance > 0, 'No funds to rescue'); | ||
| bool success = SafeERC20.safeTransfer(token, payee, balance); | ||
| require(success, 'ERC20 rescue failed'); | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
Comment on lines
+71
to
+79
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add Reentrancy Guard to The Implement a reentrancy guard to secure this function. You can reuse the same guard applied in Apply this diff to add the guard: function rescueERC20Funds(address _tokenAddress) external {
+ require(!locked, "Reentrant call detected");+ locked = true;
require(_tokenAddress != address(0), 'Invalid token address');
IERC20 token = IERC20(_tokenAddress);
uint256 balance = token.balanceOf(address(this));
require(balance > 0, 'No funds to rescue');
SafeERC20.safeTransfer(token, payee, balance);
+ locked = false;
}
| ||
| /** | ||
| * @notice Rescues any trapped funds by sending them to the payee | ||
| * @dev Can be called by anyone, but funds are always sent to the payee | ||
| */ | ||
| function rescueNativeFunds() external { | ||
| uint256 balance = address(this).balance; | ||
| require(balance > 0, 'No funds to rescue'); | ||
| (bool success, ) = payable(payee).call{value: balance}(''); | ||
| require(success, 'Rescue failed'); | ||
| } | ||
Comment on lines
+84
to
+90
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add Reentrancy Guard to The Add a reentrancy guard to this function to enhance security. Apply this diff to add the guard: function rescueNativeFunds() external {
+ require(!locked, "Reentrant call detected");+ locked = true;
uint256 balance = address(this).balance;
require(balance > 0, 'No funds to rescue');
(bool success, ) = payable(payee).call{value: balance}('');
require(success, 'Rescue failed');
+ locked = false;
}
| ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,114 @@ | ||||||||||||||||||||||||||||||||
| // SPDX-License-Identifier: MIT | ||||||||||||||||||||||||||||||||
| pragma solidity 0.8.9; | ||||||||||||||||||||||||||||||||
| import './interfaces/EthereumFeeProxy.sol'; | ||||||||||||||||||||||||||||||||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||
| import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; | ||||||||||||||||||||||||||||||||
| import './lib/SafeERC20.sol'; | ||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * @title EthereumSingleRequestProxy | ||||||||||||||||||||||||||||||||
| * @notice This contract is used to send a single request to a payee with a fee sent to a third address | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| contract EthereumSingleRequestProxy { | ||||||||||||||||||||||||||||||||
| address public payee; | ||||||||||||||||||||||||||||||||
| bytes public paymentReference; | ||||||||||||||||||||||||||||||||
| address public feeAddress; | ||||||||||||||||||||||||||||||||
| uint256 public feeAmount; | ||||||||||||||||||||||||||||||||
leoslr marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||
| IEthereumFeeProxy public ethereumFeeProxy; | ||||||||||||||||||||||||||||||||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||
| address private originalSender; | ||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * @dev Custom reentrancy guard. | ||||||||||||||||||||||||||||||||
| * Similar to OpenZeppelin's ReentrancyGuard, but allows reentrancy from ethereumFeeProxy. | ||||||||||||||||||||||||||||||||
| * This enables controlled callbacks from ethereumFeeProxy while protecting against other reentrancy attacks. | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| uint256 private constant _NOT_ENTERED = 1; | ||||||||||||||||||||||||||||||||
| uint256 private constant _ENTERED = 2; | ||||||||||||||||||||||||||||||||
| uint256 private _status; | ||||||||||||||||||||||||||||||||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||
| address _payee, | ||||||||||||||||||||||||||||||||
| bytes memory _paymentReference, | ||||||||||||||||||||||||||||||||
| address _ethereumFeeProxy, | ||||||||||||||||||||||||||||||||
| address _feeAddress, | ||||||||||||||||||||||||||||||||
| uint256 _feeAmount | ||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||
| payee = _payee; | ||||||||||||||||||||||||||||||||
| paymentReference = _paymentReference; | ||||||||||||||||||||||||||||||||
| feeAddress = _feeAddress; | ||||||||||||||||||||||||||||||||
| feeAmount = _feeAmount; | ||||||||||||||||||||||||||||||||
| ethereumFeeProxy = IEthereumFeeProxy(_ethereumFeeProxy); | ||||||||||||||||||||||||||||||||
| _status = _NOT_ENTERED; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * @dev Modified nonReentrant guard. | ||||||||||||||||||||||||||||||||
| * Prevents reentrancy except for calls from ethereumFeeProxy. | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| modifier nonReentrant() { | ||||||||||||||||||||||||||||||||
| if (msg.sender != address(ethereumFeeProxy)) { | ||||||||||||||||||||||||||||||||
| require(_status != _ENTERED, 'ReentrancyGuard: reentrant call'); | ||||||||||||||||||||||||||||||||
| _status = _ENTERED; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| _; | ||||||||||||||||||||||||||||||||
| if (msg.sender != address(ethereumFeeProxy)) { | ||||||||||||||||||||||||||||||||
| _status = _NOT_ENTERED; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| receive() external payable nonReentrant { | ||||||||||||||||||||||||||||||||
| if (msg.sender == address(ethereumFeeProxy)) { | ||||||||||||||||||||||||||||||||
| // Funds are being sent back from EthereumFeeProxy | ||||||||||||||||||||||||||||||||
| require(originalSender != address(0), 'No original sender stored'); | ||||||||||||||||||||||||||||||||
| // Forward the funds to the original sender | ||||||||||||||||||||||||||||||||
| (bool forwardSuccess, ) = payable(originalSender).call{value: msg.value}(''); | ||||||||||||||||||||||||||||||||
| require(forwardSuccess, 'Forwarding to original sender failed'); | ||||||||||||||||||||||||||||||||
| // Clear the stored original sender | ||||||||||||||||||||||||||||||||
| originalSender = address(0); | ||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| require(originalSender == address(0), 'Another request is in progress'); | ||||||||||||||||||||||||||||||||
| originalSender = msg.sender; | ||||||||||||||||||||||||||||||||
| bytes memory data = abi.encodeWithSignature( | ||||||||||||||||||||||||||||||||
| 'transferWithReferenceAndFee(address,bytes,uint256,address)', | ||||||||||||||||||||||||||||||||
| payable(payee), | ||||||||||||||||||||||||||||||||
| paymentReference, | ||||||||||||||||||||||||||||||||
| feeAmount, | ||||||||||||||||||||||||||||||||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||
| payable(feeAddress) | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
| (bool callSuccess, ) = address(ethereumFeeProxy).call{value: msg.value}(data); | ||||||||||||||||||||||||||||||||
| require(callSuccess, 'Call to EthereumFeeProxy failed'); | ||||||||||||||||||||||||||||||||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * @notice Rescues any trapped funds by sending them to the payee | ||||||||||||||||||||||||||||||||
| * @dev Can be called by anyone, but funds are always sent to the payee | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| function rescueNativeFunds() external nonReentrant { | ||||||||||||||||||||||||||||||||
| uint256 balance = address(this).balance; | ||||||||||||||||||||||||||||||||
| require(balance > 0, 'No funds to rescue'); | ||||||||||||||||||||||||||||||||
| (bool success, ) = payable(payee).call{value: balance}(''); | ||||||||||||||||||||||||||||||||
| require(success, 'Rescue failed'); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * @notice Rescues any trapped ERC20 funds by sending them to the payee | ||||||||||||||||||||||||||||||||
| * @dev Can be called by anyone, but funds are always sent to the payee | ||||||||||||||||||||||||||||||||
| * @param _tokenAddress The address of the ERC20 token to rescue | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| function rescueERC20Funds(address _tokenAddress) external nonReentrant { | ||||||||||||||||||||||||||||||||
| require(_tokenAddress != address(0), 'Invalid token address'); | ||||||||||||||||||||||||||||||||
| IERC20 token = IERC20(_tokenAddress); | ||||||||||||||||||||||||||||||||
| uint256 balance = token.balanceOf(address(this)); | ||||||||||||||||||||||||||||||||
| require(balance > 0, 'No funds to rescue'); | ||||||||||||||||||||||||||||||||
| bool success = SafeERC20.safeTransfer(token, payee, balance); | ||||||||||||||||||||||||||||||||
| require(success, 'Rescue failed'); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
Comment on lines
+106
to
+113
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix incorrect usage of The Apply this diff to fix the code: - bool success = SafeERC20.safeTransfer(token, payee, balance);- require(success, 'Rescue failed');+ SafeERC20.safeTransfer(token, payee, balance);📝 Committable suggestion
Suggested change
| ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.9; | ||
| import '@openzeppelin/contracts/access/Ownable.sol'; | ||
| import './ERC20SingleRequestProxy.sol'; | ||
| import './EthereumSingleRequestProxy.sol'; | ||
| /** | ||
| * @title SingleRequestProxyFactory | ||
| * @notice This contract is used to create SingleRequestProxy instances | ||
| */ | ||
| contract SingleRequestProxyFactory is Ownable { | ||
| /// @notice The address of the EthereumFeeProxy contract | ||
| /// @dev This proxy is used for handling Ethereum-based fee transactions | ||
| address public ethereumFeeProxy; | ||
| /// @notice The address of the ERC20FeeProxy contract | ||
| /// @dev This proxy is used for handling ERC20-based fee transactions | ||
| address public erc20FeeProxy; | ||
| event EthereumSingleRequestProxyCreated( | ||
| address indexed proxyAddress, | ||
| address indexed payee, | ||
| bytes indexed paymentReference | ||
| ); | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| event ERC20SingleRequestProxyCreated( | ||
| address indexed proxyAddress, | ||
| address indexed payee, | ||
| address tokenAddress, | ||
| bytes indexed paymentReference | ||
| ); | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| event ERC20FeeProxyUpdated(address indexed newERC20FeeProxy); | ||
| event EthereumFeeProxyUpdated(address indexed newEthereumFeeProxy); | ||
| constructor(address _ethereumFeeProxy, address _erc20FeeProxy) { | ||
| require(_ethereumFeeProxy != address(0), 'EthereumFeeProxy address cannot be zero'); | ||
| require(_erc20FeeProxy != address(0), 'ERC20FeeProxy address cannot be zero'); | ||
| ethereumFeeProxy = _ethereumFeeProxy; | ||
| erc20FeeProxy = _erc20FeeProxy; | ||
| } | ||
| /** | ||
| * @notice Creates a new EthereumSingleRequestProxy instance | ||
| * @param _payee The address of the payee | ||
| * @param _paymentReference The payment reference | ||
| * @param _feeAddress The address of the fee recipient | ||
| * @param _feeAmount The fee amount | ||
| * @return The address of the newly created proxy | ||
| */ | ||
| function createEthereumSingleRequestProxy( | ||
| address _payee, | ||
| bytes memory _paymentReference, | ||
| address _feeAddress, | ||
| uint256 _feeAmount | ||
| ) external returns (address) { | ||
| EthereumSingleRequestProxy proxy = new EthereumSingleRequestProxy( | ||
| _payee, | ||
| _paymentReference, | ||
| ethereumFeeProxy, | ||
| _feeAddress, | ||
| _feeAmount | ||
| ); | ||
| emit EthereumSingleRequestProxyCreated(address(proxy), _payee, _paymentReference); | ||
| return address(proxy); | ||
| } | ||
| /** | ||
| * @notice Creates a new ERC20SingleRequestProxy instance | ||
| * @param _payee The address of the payee | ||
| * @param _tokenAddress The address of the token | ||
| * @param _paymentReference The payment reference | ||
| * @param _feeAddress The address of the fee recipient | ||
| * @param _feeAmount The fee amount | ||
| * @return The address of the newly created proxy | ||
| */ | ||
| function createERC20SingleRequestProxy( | ||
| address _payee, | ||
| address _tokenAddress, | ||
| bytes memory _paymentReference, | ||
| address _feeAddress, | ||
| uint256 _feeAmount | ||
| ) external returns (address) { | ||
| ERC20SingleRequestProxy proxy = new ERC20SingleRequestProxy( | ||
| _payee, | ||
| _tokenAddress, | ||
| _feeAddress, | ||
| _feeAmount, | ||
| _paymentReference, | ||
| erc20FeeProxy | ||
| ); | ||
| emit ERC20SingleRequestProxyCreated(address(proxy), _payee, _tokenAddress, _paymentReference); | ||
| return address(proxy); | ||
| } | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /** | ||
| * @notice Updates the ERC20FeeProxy address | ||
| * @param _newERC20FeeProxy The new ERC20FeeProxy address | ||
| */ | ||
| function setERC20FeeProxy(address _newERC20FeeProxy) external onlyOwner { | ||
| require(_newERC20FeeProxy != address(0), 'ERC20FeeProxy address cannot be zero'); | ||
| erc20FeeProxy = _newERC20FeeProxy; | ||
| emit ERC20FeeProxyUpdated(_newERC20FeeProxy); | ||
| } | ||
| /** | ||
| * @notice Updates the EthereumFeeProxy address | ||
| * @param _newEthereumFeeProxy The new EthereumFeeProxy address | ||
| */ | ||
| function setEthereumFeeProxy(address _newEthereumFeeProxy) external onlyOwner { | ||
| require(_newEthereumFeeProxy != address(0), 'EthereumFeeProxy address cannot be zero'); | ||
| ethereumFeeProxy = _newEthereumFeeProxy; | ||
| emit EthereumFeeProxyUpdated(_newEthereumFeeProxy); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
| contract MockEthereumFeeProxy { | ||
| function transferWithReferenceAndFee( | ||
| address payable _to, | ||
| bytes calldata _paymentReference, | ||
| uint256 _feeAmount, | ||
| address payable _feeAddress | ||
| ) external payable { | ||
| // Do nothing, just accept the funds | ||
| } | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| function sendFundsBack(address payable _to, uint256 _amount) external { | ||
| (bool success, ) = _to.call{value: _amount}(''); | ||
| require(success, 'Failed to send funds back'); | ||
| } | ||
MantisClone marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| receive() external payable {} | ||
aimen74 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.