// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface NonERCToken {
function transfer(address recipient, uint256 amount) external;
function transferFrom(
address sender,
address recipient,
uint256 amount
) external;
function balanceOf(address account) external view returns (uint256);
}
contract NFTEscrow is ReentrancyGuard {
mapping(string => Listing) public listings;
mapping(string => bool) public requiresTokens;
address payable public immutable owner;
address payable public marketplaceAddress;
address payable public makerAddress;
constructor() {
owner = payable(msg.sender);
marketplaceAddress = payable(0x2FEA74160714A5Cbc556A24fAe5cCa5F29a05337);
makerAddress = payable(0x2FEA74160714A5Cbc556A24fAe5cCa5F29a05337);
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
// Define an event
event PaymentProcessed(
string listingGuid,
address buyer,
address seller,
uint256 amount
);
event ListingCreated(
address indexed sellerAddress,
string listingGuid,
uint256 amount,
string currency,
uint256 marketplaceFee,
uint256 makerFee
);
struct Listing {
string listingGuid;
address payable sellerAddress;
string buyerGuid;
address tokenAddress;
uint256 amount;
string currency;
uint256 marketplaceFee;
uint256 makerFee;
uint256 endDate;
bool isPayed;
bool isCompleted;
bool IsTokenNonErc20;
}
function createListing(
string memory listingGuid,
string memory buyerGuid,
uint256 amount,
string memory currency,
uint256 marketplaceFee,
uint256 makerFee,
uint256 endDate,
address tokenAddress,
uint256 IsTokenNonErc20
) public returns (bool) {
require(bytes(buyerGuid).length > 0, "Invalid buyerAddress");
require(amount > 0, "Invalid amount");
require(bytes(currency).length > 0, "Invalid currency");
require(bytes(listingGuid).length > 0,"Invalid listingGuid");
require(
keccak256(bytes(listings[listingGuid].listingGuid)) != keccak256(bytes(listingGuid)),
"Listing already exists"
);
bool isNonERC20 = false;
if(IsTokenNonErc20 == 1){
isNonERC20 = true;
}
if (tokenAddress != address(0)) {
requiresTokens[listingGuid] = true;
}
listings[listingGuid] = Listing({
listingGuid:listingGuid,
sellerAddress: payable(msg.sender),
buyerGuid: buyerGuid,
amount: amount,
currency: currency,
marketplaceFee: marketplaceFee,
makerFee:makerFee,
endDate:endDate,
isPayed: false,
isCompleted: false,
tokenAddress: tokenAddress,
IsTokenNonErc20: isNonERC20
});
// Emit the event
emit ListingCreated(
msg.sender,
listingGuid,
amount,
currency,
marketplaceFee,
makerFee
);
return true;
}
// function to delete listing
function removeListing(string memory listingGuid) public onlyOwner returns (bool) {
require(bytes(listings[listingGuid].listingGuid).length > 0, "Listing does not exist");
delete listings[listingGuid];
return true;
}
// function to pay for Listing Id from buyer to seller
function paySeller(
string memory listingGuid,
string memory buyerGuid,
string memory currency
) public payable returns (bool) {
Listing storage listing = listings[listingGuid];
require(keccak256(bytes(listing.buyerGuid)) == keccak256(bytes(buyerGuid)), "You are not a buyer");
require(listing.isCompleted == false, "Listing already completed");
require(keccak256(bytes(listing.listingGuid)) == keccak256(bytes(listingGuid)),"Not the correct guid");
require(
keccak256(abi.encodePacked(listing.currency)) ==
keccak256(abi.encodePacked(currency)),
"Invalid currency"
);
require(block.timestamp < listing.endDate,"Listing Expired");
if (requiresTokens[listingGuid]) {
if (listing.IsTokenNonErc20) {
NonERCToken token = NonERCToken(listing.tokenAddress);
token.transferFrom(msg.sender, address(this), listing.amount);
token.transfer(marketplaceAddress, listing.marketplaceFee);
if(listing.makerFee > 0){
token.transfer(makerAddress, listing.makerFee);
}
token.transfer(
listing.sellerAddress,
listing.amount - listing.marketplaceFee - listing.makerFee
);
} else {
// Retrieve the ERC20 token contract
IERC20 token = IERC20(listing.tokenAddress);
require(
token.transferFrom(
msg.sender,
address(this),
listing.amount
),
"Token transfer failed"
);
require(
token.transfer(marketplaceAddress, listing.marketplaceFee),
"Token transfer failed"
);
require(
token.transfer(
listing.sellerAddress,
listing.amount - listing.marketplaceFee - listing.makerFee
),
"Token transfer failed"
);
}
} else {
require(
msg.value == listing.amount,
string.concat("Incorrect amount to buy listing:",uint2str(listing.amount),"-", uint2str(msg.value))
);
listing.sellerAddress.transfer(listing.amount - listing.marketplaceFee - listing.makerFee);
marketplaceAddress.transfer(listing.marketplaceFee);
makerAddress.transfer(listing.makerFee);
}
//listing.ownerof = msg.sender;
listing.isPayed = true;
listing.isCompleted = true;
emit PaymentProcessed(
listing.listingGuid,
msg.sender,
listing.sellerAddress,
listing.amount
);
return true;
}
function addressToString(address _addr) public pure returns(string memory)
{
bytes32 value = bytes32(uint256(uint160(_addr)));
bytes memory alphabet = "0123456789abcdef";
bytes memory str = new bytes(51);
str[0] = '0';
str[1] = 'x';
for (uint256 i = 0; i < 20; i++) {
str[2+i*2] = alphabet[uint8(value[i + 12] >> 4)];
str[3+i*2] = alphabet[uint8(value[i + 12] & 0x0f)];
}
return string(str);
}
function uint2str(
uint256 _i
)
internal
pure
returns (string memory str)
{
if (_i == 0)
{
return "0";
}
uint256 j = _i;
uint256 length;
while (j != 0)
{
length++;
j /= 10;
}
bytes memory bstr = new bytes(length);
uint256 k = length;
j = _i;
while (j != 0)
{
bstr[--k] = bytes1(uint8(48 + j % 10));
j /= 10;
}
str = string(bstr);
}
// Function to check token balance
function getTokenBalance(
address tokenAddress,
address accountAddress
) external view returns (uint256) {
uint256 balance = 0;
if (isERC20Token(IERC20(tokenAddress))) {
IERC20 token = IERC20(tokenAddress);
balance = token.balanceOf(accountAddress);
} else {
NonERCToken token = NonERCToken(tokenAddress);
balance = token.balanceOf(accountAddress);
}
return balance;
}
function readListing(string memory listingGuid) public view returns (Listing memory) {
return listings[listingGuid];
}
// Function to check coin balance
function getCoinBalance(
address walletAddress
) public view returns (uint256) {
return walletAddress.balance;
}
// Function to withdraw or transfer tokens to another address
function withdrawTokens(
address tokenAddress,
address to,
uint256 amount
) external onlyOwner {
if (isERC20Token(IERC20(tokenAddress))) {
// Retrieve the ERC20 token contract
IERC20 token = IERC20(tokenAddress);
// Ensure the contract has enough balance
require(
token.balanceOf(address(this)) >= amount,
"Insufficient balance"
);
// Transfer tokens to the specified address
require(token.transfer(to, amount), "Token transfer failed");
} else {
// Retrieve the NON ERC20 token contract
NonERCToken token = NonERCToken(tokenAddress);
// Ensure the contract has enough balance
require(
token.balanceOf(address(this)) >= amount,
"Insufficient balance"
);
token.transfer(to, amount);
}
}
// Function to check if a token supports the ERC20 interface
function isERC20Token(IERC20 token) public view returns (bool) {
try token.totalSupply() returns (uint256) {
return true;
} catch (bytes memory) {
return false;
}
}
}
{
"compilationTarget": {
"contracts/escrow.sol": "NFTEscrow"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sellerAddress","type":"address"},{"indexed":false,"internalType":"string","name":"listingGuid","type":"string"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"string","name":"currency","type":"string"},{"indexed":false,"internalType":"uint256","name":"marketplaceFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"makerFee","type":"uint256"}],"name":"ListingCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"listingGuid","type":"string"},{"indexed":false,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PaymentProcessed","type":"event"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"name":"addressToString","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"listingGuid","type":"string"},{"internalType":"string","name":"buyerGuid","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"currency","type":"string"},{"internalType":"uint256","name":"marketplaceFee","type":"uint256"},{"internalType":"uint256","name":"makerFee","type":"uint256"},{"internalType":"uint256","name":"endDate","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"IsTokenNonErc20","type":"uint256"}],"name":"createListing","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"walletAddress","type":"address"}],"name":"getCoinBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"accountAddress","type":"address"}],"name":"getTokenBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"}],"name":"isERC20Token","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"listings","outputs":[{"internalType":"string","name":"listingGuid","type":"string"},{"internalType":"address payable","name":"sellerAddress","type":"address"},{"internalType":"string","name":"buyerGuid","type":"string"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"currency","type":"string"},{"internalType":"uint256","name":"marketplaceFee","type":"uint256"},{"internalType":"uint256","name":"makerFee","type":"uint256"},{"internalType":"uint256","name":"endDate","type":"uint256"},{"internalType":"bool","name":"isPayed","type":"bool"},{"internalType":"bool","name":"isCompleted","type":"bool"},{"internalType":"bool","name":"IsTokenNonErc20","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"makerAddress","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketplaceAddress","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"listingGuid","type":"string"},{"internalType":"string","name":"buyerGuid","type":"string"},{"internalType":"string","name":"currency","type":"string"}],"name":"paySeller","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"listingGuid","type":"string"}],"name":"readListing","outputs":[{"components":[{"internalType":"string","name":"listingGuid","type":"string"},{"internalType":"address payable","name":"sellerAddress","type":"address"},{"internalType":"string","name":"buyerGuid","type":"string"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"currency","type":"string"},{"internalType":"uint256","name":"marketplaceFee","type":"uint256"},{"internalType":"uint256","name":"makerFee","type":"uint256"},{"internalType":"uint256","name":"endDate","type":"uint256"},{"internalType":"bool","name":"isPayed","type":"bool"},{"internalType":"bool","name":"isCompleted","type":"bool"},{"internalType":"bool","name":"IsTokenNonErc20","type":"bool"}],"internalType":"struct NFTEscrow.Listing","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"listingGuid","type":"string"}],"name":"removeListing","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"requiresTokens","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]