Skip to content

Instantly share code, notes, and snippets.

@genecyber
Last active January 25, 2021 03:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save genecyber/153f5011821ae6fd08db73bd36de4c94 to your computer and use it in GitHub Desktop.
Save genecyber/153f5011821ae6fd08db73bd36de4c94 to your computer and use it in GitHub Desktop.
Simple NFT Order for Eth Contract Example
pragma solidity ^0.6.11;
pragma experimental ABIEncoderV2;
interface IERC721 {
function burn(uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function mint( address _to, uint256 _tokenId, string calldata _uri, string calldata _payload) external;
function ownerOf(uint256 _tokenId) external returns (address _owner);
function getApproved(uint256 _tokenId) external returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external;
}
contract Swap {
struct Order {
uint tokenId;
address nftAddress;
address buyer;
address payable seller;
uint256 price;
bool isDynamic;
}
mapping(address => bool) public witnesses;
address public owner;
uint public orderFee;
address payable benefactor;
mapping(uint => Order) public pendingOrders;
mapping(uint => Order) public completedOrders;
mapping(uint => Order) public cancelledOrders;
modifier isOwner() {
// If the first argument of 'require' evaluates to 'false', execution terminates and all
// changes to the state and to Ether balances are reverted.
// This used to consume all gas in old EVM versions, but not anymore.
// It is often a good idea to use 'require' to check if functions are called correctly.
// As a second argument, you can also provide an explanation about what went wrong.
require(msg.sender == owner, "Caller is not owner");
_;
}
constructor(uint fee) public {
orderFee = fee;
benefactor = msg.sender;
owner = msg.sender;
addWitness(msg.sender);
}
function changeFee(uint fee) isOwner() public {
orderFee = fee;
}
function changeBenefactor(address payable newBenefactor) isOwner() public {
benefactor = newBenefactor;
}
function addWitness(address _witness) public isOwner() {
witnesses[_witness] = true;
}
function removeWitness(address _witness) public isOwner() {
witnesses[_witness] = false;
}
function purchaseOrder(uint orderNumber) public payable {
Order memory order = pendingOrders[orderNumber];
require(order.isDynamic == false, 'Order is dynamically priced');
require(order.price == msg.value + orderFee, 'Not enough payment included');
require(IERC721(order.nftAddress).getApproved(order.tokenId) == address(this), 'Needs to be approved');
IERC721(order.nftAddress).safeTransferFrom(order.seller, msg.sender, order.tokenId);
order.seller.transfer(msg.value);
benefactor.transfer(orderFee);
order.buyer = msg.sender;
completedOrders[orderNumber] = order;
delete pendingOrders[orderNumber];
}
function purchaseDynamicOrder(uint orderNumber, uint256 price, bytes memory signature) public payable {
Order memory order = pendingOrders[orderNumber];
require(order.isDynamic, 'Can only buy dynamic orders');
require(witnesses[getAddressFromSignature(order.tokenId, price, signature)], 'Not Witnessed');
require(price == msg.value + orderFee, 'Not enough payment included');
require(IERC721(order.nftAddress).getApproved(order.tokenId) == address(this), 'NFT Needs to be approved');
require(price >= order.price, 'Price needs to be higher or equal to reserve');
IERC721(order.nftAddress).safeTransferFrom(order.seller, msg.sender, order.tokenId);
order.seller.transfer(msg.value);
benefactor.transfer(orderFee);
order.buyer = msg.sender;
completedOrders[orderNumber] = order;
delete pendingOrders[orderNumber];
}
function cancelOrder(uint orderNumber) public {
Order memory order = pendingOrders[orderNumber];
require(order.seller == msg.sender, 'Only order placer can cancel');
cancelledOrders[orderNumber] = order;
delete pendingOrders[orderNumber];
}
// Client side, should first call [NFTADDRESS].approve(Swap.sol.address, tokenId)
// in order to authorize this contract to transfer nft to buyer
function addOrder(address nftAddress, uint tokenId, uint256 price, uint orderNumber, bool isDynamic) public {
require(IERC721(nftAddress).getApproved(tokenId) == address(this), 'Needs to be approved');
pendingOrders[orderNumber] = Order(tokenId, nftAddress, address(this), msg.sender, price, isDynamic);
}
function getAddressFromSignature(uint256 _tokenId, uint256 _nonce, bytes memory signature) public pure returns (address) {
bytes32 hash = keccak256(abi.encodePacked(concat(uintToStr(_tokenId), uintToStr(_nonce))));
address addressFromSig = recoverSigner(hash, signature);
return addressFromSig;
}
function concat(string memory a, string memory b) internal pure returns (string memory) {
return string(abi.encodePacked(a, b));
}
/**
* @dev Recover signer address from a message by using their signature
* @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
* @param sig bytes signature, the signature is generated using web3.eth.sign(). Inclusive "0x..."
*/
function recoverSigner(bytes32 hash, bytes memory sig) internal pure returns (address) {
require(sig.length == 65, "Require correct length");
bytes32 r;
bytes32 s;
uint8 v;
// Divide the signature in r, s and v variables
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
if (v < 27) {
v += 27;
}
require(v == 27 || v == 28, "Signature version not match");
return recoverSigner2(hash, v, r, s);
}
function recoverSigner2(bytes32 h, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, h));
address addr = ecrecover(prefixedHash, v, r, s);
return addr;
}
/// @notice converts number to string
/// @dev source: https://github.com/provable-things/ethereum-api/blob/master/oraclizeAPI_0.5.sol#L1045
/// @param _i integer to convert
/// @return _uintAsString
function uintToStr(uint _i) internal pure returns (string memory _uintAsString) {
uint number = _i;
if (number == 0) {
return "0";
}
uint j = number;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len - 1;
while (number != 0) {
bstr[k--] = byte(uint8(48 + number % 10));
number /= 10;
}
return string(bstr);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment