-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
erc2309 collection edition implementation
- Loading branch information
1 parent
61e7245
commit dca8ad3
Showing
30 changed files
with
2,614 additions
and
29 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
//ERC721 interface | ||
import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | ||
|
||
/** | ||
* @dev Required interface of an ERC2309 compliant contract. | ||
*/ | ||
interface IERC2309 is IERC721 { | ||
/** | ||
* @dev Emitted when a batch of tokens is transferred from `fromAddress` to `toAddress`. | ||
*/ | ||
event ConsecutiveTransfer( | ||
uint256 indexed fromTokenId, | ||
uint256 toTokenId, | ||
address indexed fromAddress, | ||
address indexed toAddress | ||
); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# ERC2309 Collection Edition | ||
|
||
EIP2309 extends 721 version that considers the management of multiple collections of NFTs. |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "./../ERC2309.sol"; | ||
import "@openzeppelin/contracts/utils/Context.sol"; | ||
|
||
/** | ||
* @title ERC2309 Burnable Token | ||
* @dev ERC2309 Token that can be irreversibly burned (destroyed). | ||
*/ | ||
abstract contract ERC2309Burnable is Context, ERC2309 { | ||
/** | ||
* @dev Burns `tokenId`. See {ERC721-_burn}. | ||
* | ||
* Requirements: | ||
* | ||
* - The caller must own `tokenId` or be an approved operator. | ||
*/ | ||
function burn(uint256 tokenId) public virtual { | ||
//solhint-disable-next-line max-line-length | ||
require( | ||
_isApprovedOrOwner(_msgSender(), tokenId), | ||
"ERC2309Burnable: caller is not owner nor approved" | ||
); | ||
_burn(tokenId); | ||
} | ||
} |
181 changes: 181 additions & 0 deletions
181
contracts/token/ERC2309/extensions/ERC2309Collection.sol
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
//A simple way to get a counter that can only be incremented or decremented. | ||
//Very useful for ID generation, counting contract activity, among others | ||
//usage: Counters.Counter private ids | ||
// ids.increment() | ||
// ids.current() | ||
//see: https://docs.openzeppelin.com/contracts/3.x/api/utils#Counters | ||
import "@openzeppelin/contracts/utils/Counters.sol"; | ||
|
||
//implementation of ERC2309 | ||
import "./../ERC2309.sol"; | ||
|
||
abstract contract ERC2309Collection is ERC2309 { | ||
using Counters for Counters.Counter; | ||
Counters.Counter public LastCollectionId; | ||
|
||
//ex. precision 4 is 0.0000 | ||
//10000001 = 1000.0001 vs 10000001 = 10.000001 | ||
uint256 immutable internal _collectionPrecision; | ||
//collection id -> uri | ||
mapping(uint256 => string) private _collectionURIs; | ||
//collection id -> last token id | ||
mapping(uint256 => uint256) private _collectionLast; | ||
//collection id -> allowance | ||
mapping(uint256 => uint256) private _collectionAllowance; | ||
|
||
/** | ||
* @dev Constructor function | ||
*/ | ||
constructor (uint256 precision) { | ||
_collectionPrecision = precision; | ||
} | ||
|
||
/** | ||
* @dev Defines a collection | ||
*/ | ||
function _createCollection(string memory uri, uint256 allowance) | ||
internal | ||
returns(uint256) | ||
{ | ||
//allowance cannot be more than the precision allowance | ||
//to get the precision allowance of precision 4 is | ||
//(10 ** 4) - 1 = 10000 - 1 = 9999 | ||
require( | ||
allowance <= ((10 ** _collectionPrecision) - 1), | ||
"Allowance more than the max allowance" | ||
); | ||
|
||
//get the collection id | ||
LastCollectionId.increment(); | ||
uint256 collectionId = LastCollectionId.current(); | ||
|
||
//set the allowance | ||
_collectionAllowance[collectionId] = allowance; | ||
//set the collection uri | ||
_collectionURIs[collectionId] = uri; | ||
|
||
return collectionId; | ||
} | ||
|
||
/** | ||
* @dev Collection minting factory | ||
*/ | ||
function _mintCollection(uint256 collectionId, address recipient) | ||
internal | ||
returns(uint256) | ||
{ | ||
require( | ||
_collectionAllowance[collectionId] > 0, | ||
"ERC2309Collection: Collection does not exist" | ||
); | ||
|
||
//calculate the zero id | ||
//to get the zero id of precision 4 of collection id 22 | ||
//(10 ** 4) * 22 = (10000 * 22) = 220000 | ||
uint256 zeroId = (10 ** _collectionPrecision) * collectionId; | ||
//determine last id | ||
uint256 lastId = _collectionLast[collectionId]; | ||
//if no last | ||
if (lastId == 0) { | ||
//make it the zero id | ||
lastId = zeroId; | ||
} | ||
|
||
//determine the token id | ||
uint256 tokenId = lastId + 1; | ||
//determine the allowance | ||
//if zero id is 220000 and allowance is 1000 the allowance should be 221000 | ||
uint256 allowance = zeroId + _collectionAllowance[collectionId]; | ||
require( | ||
tokenId <= allowance, | ||
"ERC2309Collection: Minting exceeded for this collection" | ||
); | ||
|
||
//now mint | ||
_safeMint(recipient, tokenId); | ||
//update the last id | ||
_collectionLast[collectionId] = tokenId; | ||
|
||
return tokenId; | ||
} | ||
|
||
/** | ||
* @dev Collection minting factory | ||
*/ | ||
function _mintCollectionBatch( | ||
uint256 collectionId, | ||
address[] memory recipients | ||
) internal returns(uint256) { | ||
//calculate the zero id | ||
//to get the zero id of precision 4 of collection id 22 | ||
//(10 ** 4) * 22 = (10000 * 22) = 220000 | ||
uint256 zeroId = (10 ** _collectionPrecision) * collectionId; | ||
//determine last id | ||
uint256 lastId = _collectionLast[collectionId]; | ||
//if no last | ||
if (lastId == 0) { | ||
//make it the zero id | ||
lastId = zeroId; | ||
} | ||
|
||
//ex. from token id 2 and 3 recipients, the to token should be 4 [2, 3, 4] | ||
uint256 fromTokenId = lastId + 1; | ||
uint256 toTokenId = fromTokenId + recipients.length - 1; | ||
//determine the allowance | ||
//if zero id is 220000 and allowance is 1000 the allowance should be 221000 | ||
uint256 allowance = zeroId + _collectionAllowance[collectionId]; | ||
require( | ||
toTokenId <= allowance, | ||
"ERC2309Collection: Minting exceeded for this collection" | ||
); | ||
|
||
//now mint | ||
_mintBatchEnumerate(fromTokenId, recipients); | ||
//update the last id | ||
_collectionLast[collectionId] = toTokenId; | ||
|
||
return toTokenId; | ||
} | ||
|
||
/** | ||
* @dev Returns a collection's URI | ||
*/ | ||
function CollectionURI(uint256 collectionId) external view returns(string memory) { | ||
return _collectionURIs[collectionId]; | ||
} | ||
|
||
/** | ||
* @dev Returns a collection's URI | ||
*/ | ||
function CollectionAllowance(uint256 collectionId) | ||
external | ||
view | ||
returns(uint256) | ||
{ | ||
return _collectionAllowance[collectionId]; | ||
} | ||
|
||
/** | ||
* @dev Returns a collection id given the token id | ||
*/ | ||
function TokenCollection(uint256 tokenId) public view returns(uint256) { | ||
//to get the collection id 22 from the token id 220034 of precision 4 | ||
//220034 / (10 ** 4) = 220034 / 10000 = 22 (solidity automatically floors) | ||
return tokenId / (10 ** _collectionPrecision); | ||
} | ||
|
||
/** | ||
* @dev Returns the last token id for a collection | ||
*/ | ||
function LastCollectionToken(uint256 collectionId) | ||
public | ||
view | ||
returns(uint256) | ||
{ | ||
return _collectionLast[collectionId]; | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
contracts/token/ERC2309/extensions/ERC2309CollectionDrops.sol
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
//abstract implementation of ERC2309 | ||
//allows this contract to be treated as a token factory organized by collections | ||
import "./ERC2309Collection.sol"; | ||
|
||
//crypto logic for merkle trees | ||
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; | ||
|
||
/** | ||
* @title ERC2309 Collection Based Air Drops | ||
* @dev ERC2309 Factory where merkle drops are attached per collection | ||
*/ | ||
abstract contract ERC2309CollectionDrops is ERC2309Collection { | ||
//collection id -> root | ||
mapping(uint256 => bytes32) private _collectionRoots; | ||
|
||
/** | ||
* @dev Defines a redeemable collection | ||
*/ | ||
function _drop( | ||
string memory uri, | ||
uint256 allowance, | ||
bytes32 merkleroot | ||
) internal returns(uint256) { | ||
//create the collection first to see if there are any errors | ||
uint256 collectionId = _createCollection(uri, allowance); | ||
|
||
//next save the merkle root | ||
_collectionRoots[collectionId] = merkleroot; | ||
|
||
return collectionId; | ||
} | ||
|
||
/** | ||
* @dev Allows anyone to generally redeem anyone's token | ||
*/ | ||
function _redeem( | ||
uint256 collectionId, | ||
uint256 key, | ||
address recipient, | ||
bytes32[] calldata proof | ||
) internal returns(uint256) { | ||
//error if the proof is not verified | ||
require( | ||
//this verifies that the recipient owns this token | ||
MerkleProof.verify( | ||
proof, | ||
_collectionRoots[collectionId], | ||
//this is the leaf hash | ||
keccak256(abi.encodePacked(collectionId, key, recipient)) | ||
), | ||
"ERC2309CollectionDrops: Recipient does not own this token" | ||
); | ||
return _mintCollection(collectionId, recipient); | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
contracts/token/ERC2309/extensions/ERC2309CollectionExchange.sol
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
//custom abstract implementation of ERC2309 | ||
import "./ERC2309CollectionFees.sol"; | ||
|
||
//custom abstract implementation of ERC2309 | ||
import "./ERC2309OrderBook.sol"; | ||
|
||
/** | ||
* @title ERC2309 Collection Exchange | ||
* @dev ERC2309 Factory where buyers can buy tokens in the book and royalties are paid | ||
*/ | ||
abstract contract ERC2309CollectionExchange is | ||
ERC2309CollectionFees, | ||
ERC2309OrderBook | ||
{ | ||
/** | ||
* @dev Allows for a sender to avail of the offer price | ||
*/ | ||
function exchange(uint256 tokenId) public payable { | ||
//get listing | ||
uint256 listing = book[tokenId]; | ||
//should be a valid listing | ||
require(listing > 0, "ERC2309Exchange: Token is not listed"); | ||
//value should equal the listing amount | ||
require( | ||
msg.value == listing, | ||
"ERC2309Exchange: Amount sent does not match the listing amount" | ||
); | ||
|
||
//get collection from token | ||
uint256 collectionId = TokenCollection(tokenId); | ||
|
||
//placeholder for recipient in the loop | ||
address recipient; | ||
//release payments to recipients | ||
for (uint i = 0; i < _collectionRecipients[collectionId].length; i++) { | ||
//get the recipient | ||
recipient = _collectionRecipients[collectionId][i]; | ||
// (10 eth * 2000) / 10000 = | ||
payable(recipient).transfer( | ||
(msg.value * _collectionFees[collectionId][recipient]) / TOTAL_ALLOWABLE_FEES | ||
); | ||
} | ||
|
||
//get the token owner | ||
address payable tokenOwner = payable(ownerOf(tokenId)); | ||
//determine the remaining fee | ||
uint256 remainingFee = TOTAL_ALLOWABLE_FEES - _totalCollectionFees[collectionId]; | ||
//send the remaining fee to the token owner | ||
tokenOwner.transfer((msg.value * remainingFee) / TOTAL_ALLOWABLE_FEES); | ||
//transfer token from owner to buyer | ||
_transfer(tokenOwner, _msgSender(), tokenId); | ||
//finally delist | ||
delete book[tokenId]; | ||
//emit that something was delisted | ||
emit Delisted(tokenOwner, tokenId); | ||
} | ||
} |
Oops, something went wrong.