Skip to content

Instantly share code, notes, and snippets.

@tunnckoCore
Last active September 1, 2023 22:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tunnckoCore/0ecf85ab6a99ee4879e51c60c665935f to your computer and use it in GitHub Desktop.
Save tunnckoCore/0ecf85ab6a99ee4879e51c60c665935f to your computer and use it in GitHub Desktop.
Ethscriptions Bidding solution

EthsBidding

A simple solution to make bidding/offers functionality for the Ethscriptions Protocol. All that happens through the secure ESIP-2 transfer event.

How it works?

There is bidder, and there is an owner of ethscription. The bidder decides that it wants to make an offer (createOffer) for an ethscription, and the owner of that ethscription can choose which offer to accept.

The bidder creates a JSON ethscription and sends it to the owner of the ethscription, gets the ethscriptionId of that transaction, and calls the createOffer function with it and with the price, ethscriptionId and an expiry date. The bidder, with that call, also deposits the price it wants to pay.

The owner sees that "bid ethscription" and can go accept that offer by calling acceptOffer(bidEthscriptionId, ethscriptionId). Before that though, it should deposit the ethscription to the contract.

Bidder can cancel the bid at any time. In case when an owner has deposited its ethscription, but not called the acceptOffer yet, and the bidder cancel the offer, then the ethscription is automatically returned.

In case an offer is accepted, the other bidders must use the withdrawBid method to withdraw their funds. Meaning if you created multiple bids to one ethscription, you should call it multiple times for each bid you made.

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.21;
// Author: wgw.eth / wgw.lol / tunnckoCore
// Date: August 2023
contract EthsBidding {
struct Offer {
bytes32 bidTxHash;
bytes32 ethscriptionId;
address ethscriptionOwner;
address bidder;
uint256 price;
uint256 expiresAt;
uint256 createdAt;
}
mapping(bytes32 => Offer) public bids;
mapping(bytes32 => bool) public settles;
mapping(bytes32 => address) public deposited;
event ethscriptions_protocol_TransferEthscriptionForPreviousOwner(
address indexed previousOwner,
address indexed recipient,
bytes32 indexed id
);
function createOffer(
bytes32 bidEthscriptionId,
bytes32 tokenEthscriptionId,
address ethscriptionOwner,
uint256 price,
uint256 expiresAt
) public payable {
require(settles[bidEthscriptionId] == false, "BidAlreadyExists");
require(deposited[tokenEthscriptionId] == address(0), "AlreadyDeposited");
require(price > 0, "OfferPriceZero");
require(expiresAt > block.timestamp, "OfferExpired");
require(msg.value >= price, "InsufficientFunds");
settles[bidEthscriptionId] = true;
bids[bidEthscriptionId] = Offer(
bidEthscriptionId,
tokenEthscriptionId,
ethscriptionOwner,
msg.sender,
price,
expiresAt,
block.timestamp
);
}
function cancelOffer(bytes32 bidEthscriptionId) public {
require(settles[bidEthscriptionId], "NoSuchOffer");
Offer memory offer = bids[bidEthscriptionId];
require(offer.bidder == msg.sender, "NotBidder");
settles[bidEthscriptionId] = false;
payable(offer.bidder).transfer(offer.price);
// If ethscription is already deposited and the `accept` isn't called yet,
// but the bidder cancel the offer, then we should return the ethscription to its owner;
// and we delete from the deposits storage.
if (deposited[offer.ethscriptionId] == offer.ethscriptionOwner) {
delete deposited[offer.ethscriptionId];
// current_owner is the contract
// `previousOwner` is the ethscriptionOwner
// the `to` is the ethscriptionOwner again.
emit ethscriptions_protocol_TransferEthscriptionForPreviousOwner(
offer.ethscriptionOwner,
offer.ethscriptionOwner,
offer.ethscriptionId
);
}
}
function withdrawBid(bytes32 bidEthscriptionId) public {
Offer memory offer = bids[bidEthscriptionId];
require(offer.bidder == msg.sender, "NotBidder");
require(settles[bidEthscriptionId] == false, "OfferNotSettled");
payable(offer.bidder).transfer(offer.price);
}
function acceptOffer(
bytes32 bidEthscriptionId,
bytes32 tokenEthscriptionId
) public {
require(deposited[tokenEthscriptionId] == msg.sender, "NotDeposited");
require(settles[bidEthscriptionId], "NoSuchOffer");
Offer memory offer = bids[bidEthscriptionId];
require(
deposited[tokenEthscriptionId] == offer.ethscriptionOwner,
"NotDeposited"
);
require(block.timestamp < offer.expiresAt, "OfferExpired");
require(offer.ethscriptionOwner == msg.sender, "NotEthscriptionOwner");
settles[bidEthscriptionId] = false;
delete deposited[tokenEthscriptionId];
payable(offer.ethscriptionOwner).transfer(offer.price);
emit ethscriptions_protocol_TransferEthscriptionForPreviousOwner(
offer.ethscriptionOwner,
offer.bidder,
offer.ethscriptionId
);
}
function withdrawEthscription(bytes32 ethscriptiondId) public {
require(deposited[ethscriptiondId] == msg.sender, "NotDeposited");
delete deposited[ethscriptiondId];
emit ethscriptions_protocol_TransferEthscriptionForPreviousOwner(
msg.sender,
msg.sender,
ethscriptiondId
);
}
function _onEthscriptionDeposit(
address previousOwner,
bytes memory userCalldata
) internal virtual {
require(userCalldata.length == 32, "InvalidEthscriptionIdLength");
bytes32 ethscriptionId = abi.decode(userCalldata, (bytes32));
require(deposited[ethscriptionId] == address(0), "AlreadyDeposited");
deposited[ethscriptionId] = previousOwner;
}
fallback() external virtual {
_onEthscriptionDeposit(msg.sender, msg.data);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment