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": {
"StashMarketPlace/stashmarketplace.sol": {
"content": "// File: @openzeppelin/contracts/utils/Context.sol\r
\r
\r
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\r
\r
pragma solidity ^0.8.20;\r
\r
\r
\r
/**\r
* @dev Provides information about the current execution context, including the\r
* sender of the transaction and its data. While these are generally available\r
* via msg.sender and msg.data, they should not be accessed in such a direct\r
* manner, since when dealing with meta-transactions the account sending and\r
* paying for execution may not be the actual sender (as far as an application\r
* is concerned).\r
*\r
* This contract is only required for intermediate, library-like contracts.\r
*/\r
abstract contract Context {\r
function _msgSender() internal view virtual returns (address) {\r
return msg.sender;\r
}\r
\r
function _msgData() internal view virtual returns (bytes calldata) {\r
return msg.data;\r
}\r
\r
function _contextSuffixLength() internal view virtual returns (uint256) {\r
return 0;\r
}\r
}\r
\r
// File: @openzeppelin/contracts/access/Ownable.sol\r
\r
\r
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\r
\r
pragma solidity ^0.8.20;\r
\r
\r
/**\r
* @dev Contract module which provides a basic access control mechanism, where\r
* there is an account (an owner) that can be granted exclusive access to\r
* specific functions.\r
*\r
* The initial owner is set to the address provided by the deployer. This can\r
* later be changed with {transferOwnership}.\r
*\r
* This module is used through inheritance. It will make available the modifier\r
* `onlyOwner`, which can be applied to your functions to restrict their use to\r
* the owner.\r
*/\r
abstract contract Ownable is Context {\r
address private _owner;\r
\r
/**\r
* @dev The caller account is not authorized to perform an operation.\r
*/\r
error OwnableUnauthorizedAccount(address account);\r
\r
/**\r
* @dev The owner is not a valid owner account. (eg. `address(0)`)\r
*/\r
error OwnableInvalidOwner(address owner);\r
\r
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\r
\r
/**\r
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.\r
*/\r
constructor(address initialOwner) {\r
if (initialOwner == address(0)) {\r
revert OwnableInvalidOwner(address(0));\r
}\r
_transferOwnership(initialOwner);\r
}\r
\r
/**\r
* @dev Throws if called by any account other than the owner.\r
*/\r
modifier onlyOwner() {\r
_checkOwner();\r
_;\r
}\r
\r
/**\r
* @dev Returns the address of the current owner.\r
*/\r
function owner() public view virtual returns (address) {\r
return _owner;\r
}\r
\r
/**\r
* @dev Throws if the sender is not the owner.\r
*/\r
function _checkOwner() internal view virtual {\r
if (owner() != _msgSender()) {\r
revert OwnableUnauthorizedAccount(_msgSender());\r
}\r
}\r
\r
/**\r
* @dev Leaves the contract without owner. It will not be possible to call\r
* `onlyOwner` functions. Can only be called by the current owner.\r
*\r
* NOTE: Renouncing ownership will leave the contract without an owner,\r
* thereby disabling any functionality that is only available to the owner.\r
*/\r
function renounceOwnership() public virtual onlyOwner {\r
_transferOwnership(address(0));\r
}\r
\r
/**\r
* @dev Transfers ownership of the contract to a new account (`newOwner`).\r
* Can only be called by the current owner.\r
*/\r
function transferOwnership(address newOwner) public virtual onlyOwner {\r
if (newOwner == address(0)) {\r
revert OwnableInvalidOwner(address(0));\r
}\r
_transferOwnership(newOwner);\r
}\r
\r
/**\r
* @dev Transfers ownership of the contract to a new account (`newOwner`).\r
* Internal function without access restriction.\r
*/\r
function _transferOwnership(address newOwner) internal virtual {\r
address oldOwner = _owner;\r
_owner = newOwner;\r
emit OwnershipTransferred(oldOwner, newOwner);\r
}\r
}\r
\r
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol\r
\r
\r
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)\r
\r
pragma solidity ^0.8.20;\r
\r
/**\r
* @dev Interface of the ERC20 standard as defined in the EIP.\r
*/\r
interface IERC20 {\r
/**\r
* @dev Emitted when `value` tokens are moved from one account (`from`) to\r
* another (`to`).\r
*\r
* Note that `value` may be zero.\r
*/\r
event Transfer(address indexed from, address indexed to, uint256 value);\r
\r
/**\r
* @dev Emitted when the allowance of a `spender` for an `owner` is set by\r
* a call to {approve}. `value` is the new allowance.\r
*/\r
event Approval(address indexed owner, address indexed spender, uint256 value);\r
\r
/**\r
* @dev Returns the value of tokens in existence.\r
*/\r
function totalSupply() external view returns (uint256);\r
\r
/**\r
* @dev Returns the value of tokens owned by `account`.\r
*/\r
function balanceOf(address account) external view returns (uint256);\r
\r
/**\r
* @dev Moves a `value` amount of tokens from the caller's account to `to`.\r
*\r
* Returns a boolean value indicating whether the operation succeeded.\r
*\r
* Emits a {Transfer} event.\r
*/\r
function transfer(address to, uint256 value) external returns (bool);\r
\r
/**\r
* @dev Returns the remaining number of tokens that `spender` will be\r
* allowed to spend on behalf of `owner` through {transferFrom}. This is\r
* zero by default.\r
*\r
* This value changes when {approve} or {transferFrom} are called.\r
*/\r
function allowance(address owner, address spender) external view returns (uint256);\r
\r
/**\r
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the\r
* caller's tokens.\r
*\r
* Returns a boolean value indicating whether the operation succeeded.\r
*\r
* IMPORTANT: Beware that changing an allowance with this method brings the risk\r
* that someone may use both the old and the new allowance by unfortunate\r
* transaction ordering. One possible solution to mitigate this race\r
* condition is to first reduce the spender's allowance to 0 and set the\r
* desired value afterwards:\r
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\r
*\r
* Emits an {Approval} event.\r
*/\r
function approve(address spender, uint256 value) external returns (bool);\r
\r
/**\r
* @dev Moves a `value` amount of tokens from `from` to `to` using the\r
* allowance mechanism. `value` is then deducted from the caller's\r
* allowance.\r
*\r
* Returns a boolean value indicating whether the operation succeeded.\r
*\r
* Emits a {Transfer} event.\r
*/\r
function transferFrom(address from, address to, uint256 value) external returns (bool);\r
}\r
\r
// File: @openzeppelin/contracts/security/ReentrancyGuard.sol\r
\r
\r
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)\r
\r
pragma solidity ^0.8.0;\r
\r
/**\r
* @dev Contract module that helps prevent reentrant calls to a function.\r
*\r
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\r
* available, which can be applied to functions to make sure there are no nested\r
* (reentrant) calls to them.\r
*\r
* Note that because there is a single `nonReentrant` guard, functions marked as\r
* `nonReentrant` may not call one another. This can be worked around by making\r
* those functions `private`, and then adding `external` `nonReentrant` entry\r
* points to them.\r
*\r
* TIP: If you would like to learn more about reentrancy and alternative ways\r
* to protect against it, check out our blog post\r
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\r
*/\r
abstract contract ReentrancyGuard {\r
// Booleans are more expensive than uint256 or any type that takes up a full\r
// word because each write operation emits an extra SLOAD to first read the\r
// slot's contents, replace the bits taken up by the boolean, and then write\r
// back. This is the compiler's defense against contract upgrades and\r
// pointer aliasing, and it cannot be disabled.\r
\r
// The values being non-zero value makes deployment a bit more expensive,\r
// but in exchange the refund on every call to nonReentrant will be lower in\r
// amount. Since refunds are capped to a percentage of the total\r
// transaction's gas, it is best to keep them low in cases like this one, to\r
// increase the likelihood of the full refund coming into effect.\r
uint256 private constant _NOT_ENTERED = 1;\r
uint256 private constant _ENTERED = 2;\r
\r
uint256 private _status;\r
\r
constructor() {\r
_status = _NOT_ENTERED;\r
}\r
\r
/**\r
* @dev Prevents a contract from calling itself, directly or indirectly.\r
* Calling a `nonReentrant` function from another `nonReentrant`\r
* function is not supported. It is possible to prevent this from happening\r
* by making the `nonReentrant` function external, and making it call a\r
* `private` function that does the actual work.\r
*/\r
modifier nonReentrant() {\r
_nonReentrantBefore();\r
_;\r
_nonReentrantAfter();\r
}\r
\r
function _nonReentrantBefore() private {\r
// On the first call to nonReentrant, _status will be _NOT_ENTERED\r
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");\r
\r
// Any calls to nonReentrant after this point will fail\r
_status = _ENTERED;\r
}\r
\r
function _nonReentrantAfter() private {\r
// By storing the original value once again, a refund is triggered (see\r
// https://eips.ethereum.org/EIPS/eip-2200)\r
_status = _NOT_ENTERED;\r
}\r
\r
/**\r
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a\r
* `nonReentrant` function in the call stack.\r
*/\r
function _reentrancyGuardEntered() internal view returns (bool) {\r
return _status == _ENTERED;\r
}\r
}\r
\r
// File: contracts/Marketplace.sol\r
\r
\r
pragma solidity ^0.8.0;\r
\r
\r
\r
\r
interface IUniswapV2Locker {\r
// Getter function to fetch details about a specific lock for a user\r
function getUserLockForTokenAtIndex(\r
address user,\r
address lpAddress,\r
uint256 index\r
)\r
external\r
view\r
returns (uint256, uint256, uint256, uint256, uint256, address);\r
\r
function tokenLocks(\r
address lpAddress,\r
uint256 lockID\r
)\r
external\r
view\r
returns (uint256, uint256, uint256, uint256, uint256, address);\r
\r
// Function to transfer the ownership of a lock\r
function transferLockOwnership(\r
address lpAddress,\r
uint256 index,\r
uint256 lockID,\r
address payable newOwner\r
) external;\r
\r
function getUserNumLocksForToken(\r
address _user,\r
address _lpAddress\r
) external view returns (uint256);\r
}\r
\r
/// @title Marketplace for LP Token Lock Ownershiph\r
/// @notice This contract allows users to list and sell their Uniswap V2 LP token lock ownerships locked through Unicrypt.\r
contract Marketplace is Ownable, ReentrancyGuard {\r
// Unicrypt V2 Locker address\r
IUniswapV2Locker public uniswapV2Locker;\r
\r
// Native token address\r
IERC20 public STASH;\r
address payable public feeWallet;\r
uint256 public listingCount;\r
address public marketplaceOwner;\r
uint256 public activeListings;\r
uint256 public listedLPsCount;\r
uint256 public totalValueListedInEth;\r
uint256 public totalValueListedInSTASH;\r
uint256 public ethFee;\r
uint256 public referralBonus;\r
\r
// Zero address constant\r
address constant ZERO_ADDRESS = address(0);\r
\r
// Relevant listing info\r
struct Listing {\r
uint256 lockID;\r
uint256 listingID;\r
uint256 listingIndex;\r
address payable seller;\r
address lpAddress;\r
uint256 priceInETH;\r
uint256 priceInSTASH;\r
uint256 listDate;\r
bool isActive;\r
bool isSold;\r
address payable referral;\r
bool isVerified;\r
bool forAuction;\r
uint256 auctionIndex;\r
}\r
\r
struct Bid {\r
address bidder;\r
uint256 STASHBid;\r
uint256 ethBid;\r
uint256 listingID;\r
}\r
\r
struct ListingDetail {\r
uint256 lockID;\r
address lpAddress;\r
}\r
\r
struct AuctionDetails {\r
Bid topEthBid;\r
Bid topSTASHBid;\r
}\r
\r
// lpAddress + lockID -> returns Listing\r
mapping(address => mapping(uint256 => Listing)) public lpToLockID;\r
mapping(uint256 => ListingDetail) public listingDetail;\r
mapping(address => bool) public isLPListed;\r
mapping(address => Bid[]) public userBids;\r
mapping(address => mapping(uint256 => Bid[])) public lpBids;\r
\r
// Auctions:\r
AuctionDetails[] public auctions;\r
uint256 public auctionCount;\r
\r
// Events\r
event NewBid(\r
address indexed bidder,\r
address indexed lpAddress,\r
uint256 indexed lockID,\r
uint256 bidInSTASH,\r
uint256 bidInEth\r
);\r
event BidRedacted(\r
address indexed bidder,\r
address indexed lpAddress,\r
uint256 indexed lockId,\r
uint256 bidInSTASH,\r
uint256 bidInEth\r
);\r
event BidAccepted(\r
address indexed lpToken,\r
uint256 indexed lockId,\r
uint256 profitInEth,\r
uint256 feeEth,\r
uint256 profitInSTASH\r
);\r
event LockPurchasedWithETH(\r
address indexed lpToken,\r
uint256 indexed lockID,\r
uint256 profitInETH,\r
uint256 feeETH\r
);\r
event LockPurchasedWithSTASH(\r
address indexed lpToken,\r
uint256 indexed lockID,\r
uint256 profitInSTASH\r
);\r
event ListingInitiated(\r
address indexed lpToken,\r
uint256 indexed lockID,\r
address indexed seller,\r
bool forAuction\r
);\r
event NewActiveListing(\r
address indexed lpToken,\r
uint256 indexed lockID,\r
uint256 priceInETH,\r
uint256 priceInSTASH\r
);\r
event LockVerified(\r
address indexed lpToken,\r
uint256 indexed lockID,\r
bool status\r
);\r
event ListingRedacted(\r
address indexed lpToken,\r
uint256 indexed lockID,\r
address indexed seller\r
);\r
event ListingWithdrawn(address indexed lpToken, uint256 indexed lockID);\r
event STASHAddressUpdated(address indexed _STASHAddress);\r
event FeeAddressUpdated(address indexed _feeWallet);\r
event LockerAddressUpdated(address indexed _lockerAddress);\r
event ChangedETHFee(uint256 _ethFee);\r
event ChangedReferralBonus(uint256 _referralBonus);\r
\r
/// @notice Initialize the contract with Uniswap V2 Locker, Fee Wallet, and Token addresses\r
/// @dev Sets the contract's dependencies and the owner upon deployment\r
/// @param _uniswapV2Locker Address of the Uniswap V2 Locker contract\r
/// @param _feeWallet Address of the wallet where fees will be collected\r
/// @param _STASHAddress Address of the token contract\r
constructor(\r
address _uniswapV2Locker,\r
address payable _feeWallet,\r
address _STASHAddress\r
) Ownable(msg.sender) {\r
require(\r
_uniswapV2Locker != ZERO_ADDRESS,\r
"Invalid Uniswap V2 Locker address"\r
);\r
require(_feeWallet != ZERO_ADDRESS, "Invalid fee wallet address");\r
require(_STASHAddress != ZERO_ADDRESS, "Invalid token address");\r
\r
uniswapV2Locker = IUniswapV2Locker(_uniswapV2Locker);\r
feeWallet = _feeWallet;\r
marketplaceOwner = msg.sender;\r
STASH = IERC20(_STASHAddress);\r
ethFee = 10;\r
referralBonus = 0;\r
}\r
\r
// =====================================================================\r
// ⚡️ Public Write Functions - Modify Contract State\r
// =====================================================================\r
\r
/// @notice List an LP token lock for sale or auction\r
/// @dev The seller must be the owner of the lock and approve this contract to manage the lock\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockId The ID of the lock\r
/// @param _priceInETH The selling price in ETH (set to 0 if not selling for ETH)\r
/// @param _priceInSTASH The selling price in tokens (set to 0 if not selling for tokens)\r
/// @param _referral The address of the referrer (set to address(0) if no referral)\r
/// @param _forAuction Whether this listing is for auction or direct sale\r
function initiateListing(\r
address _lpAddress,\r
uint256 _lockId,\r
uint256 _priceInETH,\r
uint256 _priceInSTASH,\r
address payable _referral,\r
bool _forAuction\r
) external {\r
(, , , , , address owner) = uniswapV2Locker.tokenLocks(\r
_lpAddress,\r
_lockId\r
);\r
require(msg.sender == owner, "You don't own that lock.");\r
require(\r
(_priceInETH > 0) || (_priceInSTASH > 0),\r
"You must set a price in tokens or ETH"\r
);\r
\r
(bool lockFound, uint256 index) = _getIndexForUserLock(\r
_lpAddress,\r
_lockId,\r
_msgSender()\r
);\r
require(lockFound, "Lock not found!");\r
\r
uint256 newListingId;\r
AuctionDetails memory tempDetails;\r
\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
if (tempListing.listingID == 0) {\r
listingCount++;\r
newListingId = listingCount;\r
listingDetail[newListingId] = ListingDetail(_lockId, _lpAddress);\r
} else {\r
newListingId = tempListing.listingID;\r
require(!tempListing.isActive, "Listing is already active");\r
}\r
\r
if (_forAuction) {\r
tempDetails = _initializeAuctionDetails(newListingId);\r
auctions.push(tempDetails);\r
auctionCount++;\r
}\r
\r
lpToLockID[_lpAddress][_lockId] = Listing({\r
lockID: _lockId,\r
listingID: newListingId,\r
listingIndex: index,\r
seller: payable(msg.sender),\r
lpAddress: _lpAddress,\r
priceInETH: _priceInETH,\r
priceInSTASH: _priceInSTASH,\r
listDate: block.timestamp,\r
isActive: false,\r
isSold: false,\r
referral: _referral,\r
isVerified: false,\r
forAuction: _forAuction,\r
auctionIndex: _forAuction ? auctionCount - 1 : 0\r
});\r
\r
if (!isLPListed[_lpAddress]) {\r
isLPListed[_lpAddress] = true;\r
listedLPsCount++;\r
}\r
\r
totalValueListedInEth += _priceInETH;\r
totalValueListedInSTASH += _priceInSTASH;\r
\r
emit ListingInitiated(_lpAddress, _lockId, msg.sender, _forAuction);\r
}\r
\r
/// @notice Activate an initiated listing\r
/// @dev The seller must have transferred lock ownership to address(this)\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockId Unique lockID (per lpAddress) of the lock\r
function activateListing(address _lpAddress, uint256 _lockId) external {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
require(tempListing.seller == msg.sender, "Lock doesn't belong to you");\r
require(!tempListing.isActive, "Listing is already active");\r
require(!tempListing.isSold, "Listing has already been sold");\r
\r
(, , , , , address owner) = uniswapV2Locker.tokenLocks(\r
_lpAddress,\r
_lockId\r
);\r
require(owner == address(this), "Lock ownership not yet transferred");\r
\r
// Update storage\r
lpToLockID[_lpAddress][_lockId].isActive = true;\r
activeListings++;\r
\r
// Clear any existing bids for this listing\r
if (lpBids[_lpAddress][_lockId].length > 0) {\r
delete lpBids[_lpAddress][_lockId];\r
}\r
\r
emit NewActiveListing(\r
_lpAddress,\r
_lockId,\r
tempListing.priceInETH,\r
tempListing.priceInSTASH\r
);\r
}\r
\r
/// @notice Bid on a listing with Ethereum - transfer ETH to CA until bid is either beat, accepted, or withdrawn\r
/// @dev Bidder must not be listing owner.\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockId The ID of the lock\r
function bidEth(\r
address _lpAddress,\r
uint256 _lockId\r
) external payable nonReentrant {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
require(tempListing.forAuction, "Listing not for auction");\r
require(\r
tempListing.seller != msg.sender,\r
"Unable to bid on own listing"\r
);\r
require(tempListing.isActive, "Listing inactive");\r
require(!tempListing.isSold, "Listing already sold");\r
\r
AuctionDetails memory currentAuction = auctions[\r
tempListing.auctionIndex\r
];\r
\r
uint256 minBid = currentAuction.topEthBid.ethBid + 1; // Minimum increment of 1 wei\r
require(msg.value >= minBid, "Bid must be higher than current bid");\r
\r
address previousBidder = currentAuction.topEthBid.bidder;\r
uint256 previousBidAmount = currentAuction.topEthBid.ethBid;\r
\r
Bid memory newBid = Bid({\r
bidder: msg.sender,\r
STASHBid: 0,\r
ethBid: msg.value,\r
listingID: tempListing.listingID\r
});\r
\r
auctions[tempListing.auctionIndex].topEthBid = newBid;\r
userBids[msg.sender].push(newBid);\r
lpBids[_lpAddress][_lockId].push(newBid);\r
\r
if (previousBidAmount > 0) {\r
(bool success, ) = payable(previousBidder).call{\r
value: previousBidAmount\r
}("");\r
require(success, "Failed to refund previous bidder");\r
}\r
\r
emit NewBid(msg.sender, _lpAddress, _lockId, 0, msg.value);\r
}\r
\r
/// @notice Bid on a listing with tokens - transfer tokens to CA until bid is either beat, accepted, or withdrawn\r
/// @dev Bidder must not be listing owner\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockId The ID of the lock\r
/// @param _amount Amount of tokens to bid with\r
function bidSTASH(\r
address _lpAddress,\r
uint256 _lockId,\r
uint256 _amount\r
) external nonReentrant {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
require(tempListing.forAuction, "Listing not for auction");\r
require(\r
tempListing.seller != msg.sender,\r
"Unable to bid on own listing"\r
);\r
require(tempListing.isActive, "Listing inactive");\r
require(!tempListing.isSold, "Listing already sold");\r
\r
AuctionDetails memory currentAuction = auctions[\r
tempListing.auctionIndex\r
];\r
\r
uint256 minBid = currentAuction.topSTASHBid.STASHBid + 1; // Minimum increment of 1 token\r
require(_amount >= minBid, "Bid must be higher than current bid");\r
\r
address previousBidder = currentAuction.topSTASHBid.bidder;\r
uint256 previousBidAmount = currentAuction.topSTASHBid.STASHBid;\r
\r
require(\r
STASH.transferFrom(msg.sender, address(this), _amount),\r
"Failed to transfer tokens"\r
);\r
\r
Bid memory newBid = Bid({\r
bidder: msg.sender,\r
STASHBid: _amount,\r
ethBid: 0,\r
listingID: tempListing.listingID\r
});\r
\r
auctions[tempListing.auctionIndex].topSTASHBid = newBid;\r
userBids[msg.sender].push(newBid);\r
lpBids[_lpAddress][_lockId].push(newBid);\r
\r
if (previousBidAmount > 0) {\r
require(\r
STASH.transfer(previousBidder, previousBidAmount),\r
"Failed to refund previous bidder"\r
);\r
}\r
\r
emit NewBid(msg.sender, _lpAddress, _lockId, _amount, 0);\r
}\r
\r
function acceptBid(\r
address _lpAddress,\r
uint256 _lockId,\r
bool _eth\r
) external nonReentrant {\r
Listing storage tempListing = lpToLockID[_lpAddress][_lockId];\r
require(tempListing.isActive, "Listing is not active");\r
require(!tempListing.isSold, "Listing is already sold");\r
require(tempListing.forAuction, "Listing is not for auction");\r
require(\r
tempListing.seller == msg.sender,\r
"Only the seller can accept a bid"\r
);\r
\r
AuctionDetails storage tempAuction = auctions[tempListing.auctionIndex];\r
\r
Bid storage topBid = _eth\r
? tempAuction.topEthBid\r
: tempAuction.topSTASHBid;\r
require(\r
(_eth && topBid.ethBid > 0) || (!_eth && topBid.STASHBid > 0),\r
"No valid bid to accept"\r
);\r
\r
// Return the other type of bid if it exists\r
if (_eth && tempAuction.topSTASHBid.STASHBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
false,\r
tempListing,\r
tempAuction.topSTASHBid.bidder\r
);\r
} else if (!_eth && tempAuction.topEthBid.ethBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
true,\r
tempListing,\r
tempAuction.topEthBid.bidder\r
);\r
}\r
\r
_winAuction(tempListing, topBid, _eth);\r
}\r
\r
/// @notice Redact your bid on select lock - must be done prior to the expiry date of auction.\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockId The ID of the lock\r
/// @param _eth True if bidder is redacting a bid in ETH, false if bid is in tokens\r
function redactBid(\r
address _lpAddress,\r
uint256 _lockId,\r
bool _eth\r
) external nonReentrant {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
require(tempListing.forAuction, "No auction for this listing");\r
require(tempListing.isActive, "Auction is not active");\r
\r
AuctionDetails memory currentAuction = auctions[\r
tempListing.auctionIndex\r
];\r
\r
if (_eth) {\r
require(currentAuction.topEthBid.ethBid > 0, "No ETH bid present");\r
} else {\r
require(\r
currentAuction.topSTASHBid.STASHBid > 0,\r
"No token bid present"\r
);\r
}\r
\r
_returnBid(_lpAddress, _lockId, _eth, tempListing, msg.sender);\r
}\r
\r
/// @notice Purchase a listed LP token lock with ETH\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockId The ID of the lock\r
function buyLockWithETH(\r
address _lpAddress,\r
uint256 _lockId\r
) external payable nonReentrant {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
require(tempListing.isActive, "Listing must be active.");\r
require(tempListing.priceInETH > 0, "Listing not for sale in ETH.");\r
require(\r
msg.value == tempListing.priceInETH,\r
"Incorrect amount of ETH."\r
);\r
\r
(bool lockFound, uint256 index) = _getIndex(_lpAddress, tempListing);\r
require(lockFound, "Mismatch in inputs");\r
\r
uint256 feeAmount = (msg.value * ethFee) / 100;\r
uint256 toPay = msg.value - feeAmount;\r
\r
if (tempListing.referral != ZERO_ADDRESS) {\r
uint256 feeForReferral = (feeAmount * referralBonus) / 100;\r
feeAmount = feeAmount - feeForReferral;\r
(bool success, ) = tempListing.referral.call{value: feeForReferral}(\r
""\r
);\r
require(success, "Referral fee transfer failed");\r
}\r
\r
(bool feeSuccess, ) = feeWallet.call{value: feeAmount}("");\r
require(feeSuccess, "Fee transfer failed");\r
\r
(bool sellerSuccess, ) = payable(tempListing.seller).call{value: toPay}(\r
""\r
);\r
require(sellerSuccess, "Seller payment failed");\r
\r
if (tempListing.forAuction) {\r
AuctionDetails memory currentAuction = auctions[\r
tempListing.auctionIndex\r
];\r
\r
if (currentAuction.topSTASHBid.STASHBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
false,\r
tempListing,\r
currentAuction.topSTASHBid.bidder\r
);\r
}\r
if (currentAuction.topEthBid.ethBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
true,\r
tempListing,\r
currentAuction.topEthBid.bidder\r
);\r
}\r
}\r
\r
lpToLockID[_lpAddress][_lockId].isActive = false;\r
lpToLockID[_lpAddress][_lockId].isSold = true;\r
activeListings--;\r
\r
uniswapV2Locker.transferLockOwnership(\r
_lpAddress,\r
index,\r
_lockId,\r
payable(msg.sender)\r
);\r
\r
emit LockPurchasedWithETH(\r
tempListing.lpAddress,\r
tempListing.lockID,\r
toPay,\r
feeAmount\r
);\r
}\r
\r
/// @notice Purchase a listed LP token lock with tokens\r
/// @dev Requires approval to transfer tokens to cover the purchase price\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockId The ID of the lock\r
function buyLockWithSTASH(\r
address _lpAddress,\r
uint256 _lockId\r
) external nonReentrant {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
\r
require(tempListing.isActive, "Listing must be active");\r
require(tempListing.priceInSTASH > 0, "Listing not for sale in tokens");\r
require(\r
STASH.balanceOf(msg.sender) >= tempListing.priceInSTASH,\r
"Insufficient STASH"\r
);\r
\r
(bool lockFound, uint256 index) = _getIndex(_lpAddress, tempListing);\r
require(lockFound, "Mismatch in inputs");\r
\r
// Transfer tokens from buyer to seller\r
require(\r
STASH.transferFrom(\r
msg.sender,\r
tempListing.seller,\r
tempListing.priceInSTASH\r
),\r
"Tokens transfer failed"\r
);\r
\r
if (tempListing.forAuction) {\r
AuctionDetails memory currentAuction = auctions[\r
tempListing.auctionIndex\r
];\r
\r
if (currentAuction.topSTASHBid.STASHBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
false,\r
tempListing,\r
currentAuction.topSTASHBid.bidder\r
);\r
}\r
if (currentAuction.topEthBid.ethBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
true,\r
tempListing,\r
currentAuction.topEthBid.bidder\r
);\r
}\r
}\r
\r
lpToLockID[_lpAddress][_lockId].isActive = false;\r
lpToLockID[_lpAddress][_lockId].isSold = true;\r
activeListings--;\r
\r
uniswapV2Locker.transferLockOwnership(\r
_lpAddress,\r
index,\r
_lockId,\r
payable(msg.sender)\r
);\r
\r
emit LockPurchasedWithSTASH(\r
tempListing.lpAddress,\r
tempListing.lockID,\r
tempListing.priceInSTASH\r
);\r
}\r
\r
/// @notice Withdraw a listed LP token lock\r
/// @dev Only the seller can withdraw the listing\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockId The ID of the lock\r
function withdrawListing(\r
address _lpAddress,\r
uint256 _lockId\r
) external nonReentrant {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
require(\r
tempListing.seller == msg.sender,\r
"This listing does not belong to you."\r
);\r
\r
(, , , , , address owner) = uniswapV2Locker.tokenLocks(\r
_lpAddress,\r
_lockId\r
);\r
require(owner == address(this), "Marketplace does not own your lock");\r
\r
(bool lockFound, uint256 index) = _getIndex(_lpAddress, tempListing);\r
require(lockFound, "Mismatch in inputs.");\r
\r
if (tempListing.forAuction) {\r
AuctionDetails memory currentAuction = auctions[\r
tempListing.auctionIndex\r
];\r
\r
if (currentAuction.topSTASHBid.STASHBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
false,\r
tempListing,\r
currentAuction.topSTASHBid.bidder\r
);\r
}\r
\r
if (currentAuction.topEthBid.ethBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
true,\r
tempListing,\r
currentAuction.topEthBid.bidder\r
);\r
}\r
}\r
\r
if (tempListing.isActive) {\r
lpToLockID[_lpAddress][_lockId].isActive = false;\r
activeListings--;\r
}\r
\r
uniswapV2Locker.transferLockOwnership(\r
_lpAddress,\r
index,\r
_lockId,\r
payable(msg.sender)\r
);\r
\r
emit ListingWithdrawn(_lpAddress, _lockId);\r
}\r
\r
/// @notice Change the ETH price of a listing\r
/// @dev Only seller can change price\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockID Unique lock ID (per lpAddress) of the lock\r
/// @param newPriceInETH Updated ETH price of listing\r
function changePriceInETH(\r
address _lpAddress,\r
uint256 _lockID,\r
uint256 newPriceInETH\r
) external nonReentrant {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockID];\r
require(\r
tempListing.seller == msg.sender,\r
"This listing does not belong to you."\r
);\r
require(newPriceInETH > 0, "Price must be greater than zero");\r
\r
lpToLockID[_lpAddress][_lockID].priceInETH = newPriceInETH;\r
}\r
\r
/// @notice Change the price of a listing in tokens\r
/// @dev Only seller can change price\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockID Unique lock ID (per lpAddress) of the lock\r
/// @param newPriceInSTASH Updated price of listing\r
function changePriceInSTASH(\r
address _lpAddress,\r
uint256 _lockID,\r
uint256 newPriceInSTASH\r
) external nonReentrant {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockID];\r
require(\r
tempListing.seller == msg.sender,\r
"This listing does not belong to you."\r
);\r
require(newPriceInSTASH > 0, "Price must be greater than zero");\r
\r
lpToLockID[_lpAddress][_lockID].priceInSTASH = newPriceInSTASH;\r
}\r
\r
// =====================================================================\r
// ???? Public Read Functions - Query Contract State\r
// =====================================================================\r
\r
function userBidsCount(address _user) external view returns (uint256) {\r
return userBids[_user].length;\r
}\r
\r
function lpBidsCount(\r
address _lpAddress,\r
uint256 _lockID\r
) public view returns (uint256) {\r
return lpBids[_lpAddress][_lockID].length;\r
}\r
\r
function getIndex(\r
address _user,\r
address _lpAddress,\r
uint256 _lockId\r
) external view returns (bool, uint256) {\r
return _getIndexForUserLock(_lpAddress, _lockId, _user);\r
}\r
\r
// =====================================================================\r
// ???? Only Owner Functions - Restricted Access\r
// =====================================================================\r
\r
/// @notice Set the referral fee (in percentage)\r
/// @dev This function can only be called by the contract owner\r
/// @param _referralBonus Referral fee percentage for buyLockWithETH\r
function setReferralFee(uint256 _referralBonus) external onlyOwner {\r
require(\r
_referralBonus <= 50,\r
"Maximum referral bonus is 50% of the fee"\r
);\r
require(referralBonus != _referralBonus, "You must change the bonus");\r
referralBonus = _referralBonus;\r
emit ChangedReferralBonus(_referralBonus);\r
}\r
\r
/// @notice Set the eth fee (in percentage)\r
/// @dev This function can only be called by the contract owner\r
/// @param _ethFee Fee percentage for buyLockWithETH\r
function setETHFee(uint256 _ethFee) external onlyOwner {\r
require(_ethFee <= 10, "Maximum fee is 10%");\r
require(ethFee != _ethFee, "You must change the fee");\r
ethFee = _ethFee;\r
emit ChangedETHFee(_ethFee);\r
}\r
\r
/// @notice Set the address of the token\r
/// @dev This function can only be called by the contract owner\r
/// @param _STASHAddress The address of the token contract\r
function setSTASHAddress(address _STASHAddress) external onlyOwner {\r
require(\r
address(STASH) != _STASHAddress,\r
"Must input different contract address"\r
);\r
require(\r
_STASHAddress != ZERO_ADDRESS,\r
"Cannot set STASH address as zero address"\r
);\r
STASH = IERC20(_STASHAddress);\r
emit STASHAddressUpdated(_STASHAddress);\r
}\r
\r
/// @notice Set the address of the fee wallet\r
/// @dev This function can only be called by the contract owner\r
/// @param _feeWallet The address of the new fee wallet\r
function setFeeWallet(address payable _feeWallet) external onlyOwner {\r
require(feeWallet != _feeWallet, "Same wallet");\r
require(\r
_feeWallet != ZERO_ADDRESS,\r
"Cannot set fee wallet as zero address"\r
);\r
feeWallet = _feeWallet;\r
emit FeeAddressUpdated(_feeWallet);\r
}\r
\r
/// @notice Set the address of the liquidity locker\r
/// @dev This function can only be called by the contract owner\r
/// @param _uniswapV2Locker The address of the new liquidity locker\r
function setLockerAddress(address _uniswapV2Locker) external onlyOwner {\r
require(\r
address(uniswapV2Locker) != _uniswapV2Locker,\r
"Must input different contract address"\r
);\r
require(\r
_uniswapV2Locker != ZERO_ADDRESS,\r
"Cannot set locker address as zero address"\r
);\r
uniswapV2Locker = IUniswapV2Locker(_uniswapV2Locker);\r
emit LockerAddressUpdated(_uniswapV2Locker);\r
}\r
\r
/// @notice Verify a listing as safe\r
/// @dev Only owner can verify listings\r
/// @param _lpAddress Address of the LP token\r
/// @param _lockID Unique lock ID (per lpAddress) of the lock\r
/// @param _status Status of verification\r
function verifyListing(\r
address _lpAddress,\r
uint256 _lockID,\r
bool _status\r
) external onlyOwner {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockID];\r
require(\r
_status != tempListing.isVerified,\r
"Must change listing status"\r
);\r
lpToLockID[_lpAddress][_lockID].isVerified = _status;\r
emit LockVerified(_lpAddress, _lockID, _status);\r
}\r
\r
/// @notice Return ownership of a lock to the original seller and remove the listing\r
/// @dev Only the contract owner can call this function\r
/// @param _lpAddress Address of the LP token associated with the lock\r
/// @param _lockId The ID of the lock to be redacted\r
function redactListing(\r
address _lpAddress,\r
uint256 _lockId\r
) external onlyOwner {\r
Listing memory tempListing = lpToLockID[_lpAddress][_lockId];\r
\r
require(tempListing.seller != address(0), "Listing does not exist.");\r
\r
(bool lockFound, uint256 index) = _getIndex(_lpAddress, tempListing);\r
require(lockFound, "Lock not found.");\r
\r
if (tempListing.forAuction) {\r
AuctionDetails memory currentAuction = auctions[\r
tempListing.auctionIndex\r
];\r
\r
if (\r
currentAuction.topSTASHBid.STASHBid > 0 &&\r
currentAuction.topEthBid.ethBid > 0\r
) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
true,\r
tempListing,\r
currentAuction.topEthBid.bidder\r
);\r
\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
false,\r
tempListing,\r
currentAuction.topSTASHBid.bidder\r
);\r
} else if (currentAuction.topEthBid.ethBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
true,\r
tempListing,\r
currentAuction.topEthBid.bidder\r
);\r
} else if (currentAuction.topSTASHBid.STASHBid > 0) {\r
_returnBid(\r
_lpAddress,\r
_lockId,\r
false,\r
tempListing,\r
currentAuction.topSTASHBid.bidder\r
);\r
}\r
}\r
\r
uniswapV2Locker.transferLockOwnership(\r
_lpAddress,\r
index,\r
_lockId,\r
tempListing.seller\r
);\r
\r
if (tempListing.isActive) {\r
lpToLockID[_lpAddress][_lockId].isActive = false;\r
activeListings--;\r
}\r
\r
delete lpToLockID[_lpAddress][_lockId];\r
emit ListingRedacted(_lpAddress, _lockId, tempListing.seller);\r
}\r
\r
// =====================================================================\r
// ???? Private/Internal Functions - Contract's Inner Workings\r
// =====================================================================\r
\r
function _initializeAuctionDetails(\r
uint256 _listingId\r
) internal pure returns (AuctionDetails memory) {\r
return\r
AuctionDetails({\r
topEthBid: Bid({\r
bidder: ZERO_ADDRESS,\r
STASHBid: 0,\r
ethBid: 0,\r
listingID: _listingId\r
}),\r
topSTASHBid: Bid({\r
bidder: ZERO_ADDRESS,\r
STASHBid: 0,\r
ethBid: 0,\r
listingID: _listingId\r
})\r
});\r
}\r
\r
function _winAuction(\r
Listing storage _tempListing,\r
Bid storage _winningBid,\r
bool _eth\r
) private {\r
require(_tempListing.isActive, "Listing must be active.");\r
(bool lockFound, uint256 index) = _getIndex(\r
_tempListing.lpAddress,\r
_tempListing\r
);\r
require(lockFound, "Mismatch in inputs");\r
\r
uint256 profitInEth = 0;\r
uint256 feeEth = 0;\r
uint256 profitInSTASH = 0;\r
\r
if (_eth) {\r
require(\r
address(this).balance >= _winningBid.ethBid,\r
"Insufficient contract balance"\r
);\r
feeEth = (_winningBid.ethBid * ethFee) / 100;\r
profitInEth = _winningBid.ethBid - feeEth;\r
\r
if (_tempListing.referral != ZERO_ADDRESS) {\r
uint256 feeForReferral = (feeEth * referralBonus) / 100;\r
feeEth -= feeForReferral;\r
(bool referralSuccess, ) = _tempListing.referral.call{\r
value: feeForReferral\r
}("");\r
require(referralSuccess, "Failed to send referral fee");\r
}\r
\r
(bool feeSuccess, ) = feeWallet.call{value: feeEth}("");\r
require(feeSuccess, "Failed to send fee");\r
\r
(bool sellerSuccess, ) = payable(_tempListing.seller).call{\r
value: profitInEth\r
}("");\r
require(sellerSuccess, "Failed to send payment to seller");\r
\r
_winningBid.ethBid = 0;\r
} else {\r
require(\r
STASH.balanceOf(address(this)) >= _winningBid.STASHBid,\r
"Insufficient STASH balance"\r
);\r
\r
profitInSTASH = _winningBid.STASHBid;\r
require(\r
STASH.transfer(_tempListing.seller, profitInSTASH),\r
"Failed to transfer tokens to seller"\r
);\r
_winningBid.STASHBid = 0;\r
}\r
\r
_tempListing.isActive = false;\r
_tempListing.isSold = true;\r
activeListings--;\r
\r
uniswapV2Locker.transferLockOwnership(\r
_tempListing.lpAddress,\r
index,\r
_tempListing.lockID,\r
payable(_winningBid.bidder)\r
);\r
\r
emit BidAccepted(\r
_tempListing.lpAddress,\r
_tempListing.lockID,\r
profitInEth,\r
feeEth,\r
profitInSTASH\r
);\r
}\r
\r
function _returnBid(\r
address _lpAddress,\r
uint256 _lockId,\r
bool _eth,\r
Listing memory _tempListing,\r
address _sender\r
) internal {\r
AuctionDetails storage currentAuction = auctions[\r
_tempListing.auctionIndex\r
];\r
\r
Bid storage topBid = _eth\r
? currentAuction.topEthBid\r
: currentAuction.topSTASHBid;\r
\r
require(\r
topBid.bidder == _sender,\r
_eth\r
? "You are not the current ETH bidder"\r
: "You are not the current token bidder"\r
);\r
\r
uint256 amount = _eth ? topBid.ethBid : topBid.STASHBid;\r
\r
// Reset the bid\r
topBid.bidder = address(0);\r
topBid.ethBid = 0;\r
topBid.STASHBid = 0;\r
topBid.listingID = _tempListing.listingID;\r
\r
if (amount > 0) {\r
if (_eth) {\r
payable(_sender).transfer(amount);\r
} else {\r
require(\r
STASH.transfer(_sender, amount),\r
"Failed to transfer tokens"\r
);\r
}\r
\r
emit BidRedacted(\r
_sender,\r
_lpAddress,\r
_lockId,\r
_eth ? 0 : amount,\r
_eth ? amount : 0\r
);\r
}\r
}\r
\r
/// @notice Find unique (per lpAddress) lock index in order to transfer lock ownership\r
/// @param _lpAddress Address of the LP token\r
/// @param _listing Listing in question\r
/// @return lockFound Boolean indicating if the lock was found\r
/// @return index The index of the found lock\r
function _getIndex(\r
address _lpAddress,\r
Listing memory _listing\r
) internal view returns (bool lockFound, uint256 index) {\r
uint256 numLocksAtAddress = uniswapV2Locker.getUserNumLocksForToken(\r
address(this),\r
_lpAddress\r
);\r
\r
if (numLocksAtAddress == 0) {\r
return (false, 0);\r
}\r
\r
if (numLocksAtAddress == 1) {\r
return (true, 0);\r
}\r
\r
for (uint256 i = 0; i < numLocksAtAddress; i++) {\r
(, , , , uint256 _lockId, ) = uniswapV2Locker\r
.getUserLockForTokenAtIndex(address(this), _lpAddress, i);\r
if (_lockId == _listing.lockID) {\r
return (true, i);\r
}\r
}\r
\r
return (false, 0);\r
}\r
\r
function _getIndexForUserLock(\r
address _lpAddress,\r
uint256 _lockId,\r
address user\r
) internal view returns (bool lockFound, uint256 index) {\r
uint256 numLocksAtAddress = uniswapV2Locker.getUserNumLocksForToken(\r
user,\r
_lpAddress\r
);\r
\r
if (numLocksAtAddress == 0) {\r
return (false, 0);\r
}\r
\r
if (numLocksAtAddress == 1) {\r
return (true, 0);\r
}\r
\r
for (uint256 i = 0; i < numLocksAtAddress; i++) {\r
(, , , , uint256 _tempLockID, ) = uniswapV2Locker\r
.getUserLockForTokenAtIndex(user, _lpAddress, i);\r
if (_tempLockID == _lockId) {\r
return (true, i);\r
}\r
}\r
\r
return (false, 0);\r
}\r
}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": [],
"evmVersion": "cancun"
}
}}
Submitted on: 2025-10-03 20:04:06
Comments
Log in to comment.
No comments yet.