Last active
January 25, 2021 03:25
-
-
Save genecyber/153f5011821ae6fd08db73bd36de4c94 to your computer and use it in GitHub Desktop.
Simple NFT Order for Eth Contract Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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