Skip to content

Commit 971ceb2

Browse files
nkrishangKrishang Nadgauda
andauthored
Solidity SDK ERC1155 (#206)
* Add ERC1155Base * Update batchMintTo * add ERC1155 LazyMint * add ERC1155DelayedReveal * Add ERC1155SignatureMint * Add DropSinglePhase1155 and ERC1155Drop * add totalSupply + fix batchMintTo * Add tests for ERC1155Base * add tests for ERC1155LazyMint * add tests for ERC1155DelayedReveal * run prettier * update test file names * wip ERC1155SignatureMint test * Add tests for ERC1155SignatureMint * bug fix: set claimCondition to updated in-memory var * Add tests for ERC1155Drop * run prettier * wip code comments ERC1155 bases * forge update * update code comments for ERC1155 bases * Update eip/ERC1155 contract * docs update; run prettier * Update delayed reveal in ERC1155 bases * docs update * pkg release Co-authored-by: Krishang Nadgauda <nkrishang@Krishangs-MBP.lan>
1 parent 77ce1d1 commit 971ceb2

27 files changed

+8421
-7
lines changed

contracts/base/ERC1155Base.sol

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import { ERC1155 } from "../eip/ERC1155.sol";
5+
6+
import "../extension/ContractMetadata.sol";
7+
import "../extension/Multicall.sol";
8+
import "../extension/Ownable.sol";
9+
import "../extension/Royalty.sol";
10+
import "../extension/BatchMintMetadata.sol";
11+
12+
import "../lib/TWStrings.sol";
13+
14+
/**
15+
* The `ERC1155Base` smart contract implements the ERC1155 NFT standard.
16+
* It includes the following additions to standard ERC1155 logic:
17+
*
18+
* - Ability to mint NFTs via the provided `mintTo` and `batchMintTo` functions.
19+
*
20+
* - Contract metadata for royalty support on platforms such as OpenSea that use
21+
* off-chain information to distribute roaylties.
22+
*
23+
* - Ownership of the contract, with the ability to restrict certain functions to
24+
* only be called by the contract's owner.
25+
*
26+
* - Multicall capability to perform multiple actions atomically
27+
*
28+
* - EIP 2981 compliance for royalty support on NFT marketplaces.
29+
*/
30+
31+
contract ERC1155Base is ERC1155, ContractMetadata, Ownable, Royalty, Multicall, BatchMintMetadata {
32+
using TWStrings for uint256;
33+
34+
/*//////////////////////////////////////////////////////////////
35+
State variables
36+
//////////////////////////////////////////////////////////////*/
37+
38+
/// @dev The tokenId of the next NFT to mint.
39+
uint256 internal nextTokenIdToMint_;
40+
41+
/*//////////////////////////////////////////////////////////////
42+
Mappings
43+
//////////////////////////////////////////////////////////////*/
44+
45+
/**
46+
* @notice Returns the total supply of NFTs of a given tokenId
47+
* @dev Mapping from tokenId => total circulating supply of NFTs of that tokenId.
48+
*/
49+
mapping(uint256 => uint256) public totalSupply;
50+
51+
/*//////////////////////////////////////////////////////////////
52+
Constructor
53+
//////////////////////////////////////////////////////////////*/
54+
55+
constructor(
56+
string memory _name,
57+
string memory _symbol,
58+
address _royaltyRecipient,
59+
uint128 _royaltyBps
60+
) ERC1155(_name, _symbol) {
61+
_setupOwner(msg.sender);
62+
_setupDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps);
63+
}
64+
65+
/*//////////////////////////////////////////////////////////////
66+
Overriden metadata logic
67+
//////////////////////////////////////////////////////////////*/
68+
69+
/// @notice Returns the metadata URI for the given tokenId.
70+
function uri(uint256 _tokenId) public view virtual override returns (string memory) {
71+
string memory uriForToken = _uri[_tokenId];
72+
if (bytes(uriForToken).length > 0) {
73+
return uriForToken;
74+
}
75+
76+
string memory batchUri = getBaseURI(_tokenId);
77+
return string(abi.encodePacked(batchUri, _tokenId.toString()));
78+
}
79+
80+
/*//////////////////////////////////////////////////////////////
81+
Mint / burn logic
82+
//////////////////////////////////////////////////////////////*/
83+
84+
/**
85+
* @notice Lets an authorized address mint NFTs to a recipient.
86+
* @dev - The logic in the `_canMint` function determines whether the caller is authorized to mint NFTs.
87+
* - If `_tokenId == type(uint256).max` a new NFT at tokenId `nextTokenIdToMint` is minted. If the given
88+
* `tokenId < nextTokenIdToMint`, then additional supply of an existing NFT is being minted.
89+
*
90+
* @param _to The recipient of the NFTs to mint.
91+
* @param _tokenId The tokenId of the NFT to mint.
92+
* @param _tokenURI The full metadata URI for the NFTs minted (if a new NFT is being minted).
93+
* @param _amount The amount of the same NFT to mint.
94+
*/
95+
function mintTo(
96+
address _to,
97+
uint256 _tokenId,
98+
string memory _tokenURI,
99+
uint256 _amount
100+
) public virtual {
101+
require(_canMint(), "Not authorized to mint.");
102+
103+
uint256 tokenIdToMint;
104+
uint256 nextIdToMint = nextTokenIdToMint();
105+
106+
if (_tokenId == type(uint256).max) {
107+
tokenIdToMint = nextIdToMint;
108+
nextTokenIdToMint_ += 1;
109+
_setTokenURI(nextIdToMint, _tokenURI);
110+
} else {
111+
require(_tokenId < nextIdToMint, "invalid id");
112+
tokenIdToMint = _tokenId;
113+
}
114+
115+
_mint(_to, tokenIdToMint, _amount, "");
116+
}
117+
118+
/**
119+
* @notice Lets an authorized address mint multiple NEW NFTs at once to a recipient.
120+
* @dev The logic in the `_canMint` function determines whether the caller is authorized to mint NFTs.
121+
* If `_tokenIds[i] == type(uint256).max` a new NFT at tokenId `nextTokenIdToMint` is minted. If the given
122+
* `tokenIds[i] < nextTokenIdToMint`, then additional supply of an existing NFT is minted.
123+
* The metadata for each new NFT is stored at `baseURI/{tokenID of NFT}`
124+
*
125+
* @param _to The recipient of the NFT to mint.
126+
* @param _tokenIds The tokenIds of the NFTs to mint.
127+
* @param _amounts The amounts of each NFT to mint.
128+
* @param _baseURI The baseURI for the `n` number of NFTs minted. The metadata for each NFT is `baseURI/tokenId`
129+
*/
130+
function batchMintTo(
131+
address _to,
132+
uint256[] memory _tokenIds,
133+
uint256[] memory _amounts,
134+
string memory _baseURI
135+
) public virtual {
136+
require(_canMint(), "Not authorized to mint.");
137+
require(_amounts.length > 0, "Minting zero tokens.");
138+
require(_tokenIds.length == _amounts.length, "Length mismatch.");
139+
140+
uint256 nextIdToMint = nextTokenIdToMint();
141+
uint256 startNextIdToMint = nextIdToMint;
142+
143+
uint256 numOfNewNFTs;
144+
145+
for (uint256 i = 0; i < _tokenIds.length; i += 1) {
146+
if (_tokenIds[i] == type(uint256).max) {
147+
_tokenIds[i] = nextIdToMint;
148+
149+
nextIdToMint += 1;
150+
numOfNewNFTs += 1;
151+
} else {
152+
require(_tokenIds[i] < nextIdToMint, "invalid id");
153+
}
154+
}
155+
156+
if (numOfNewNFTs > 0) {
157+
_batchMintMetadata(startNextIdToMint, numOfNewNFTs, _baseURI);
158+
}
159+
160+
nextTokenIdToMint_ = nextIdToMint;
161+
_mintBatch(_to, _tokenIds, _amounts, "");
162+
}
163+
164+
/**
165+
* @notice Lets an owner or approved operator burn NFTs of the given tokenId.
166+
*
167+
* @param _owner The owner of the NFT to burn.
168+
* @param _tokenId The tokenId of the NFT to burn.
169+
* @param _amount The amount of the NFT to burn.
170+
*/
171+
function burn(
172+
address _owner,
173+
uint256 _tokenId,
174+
uint256 _amount
175+
) external virtual {
176+
address caller = msg.sender;
177+
178+
require(caller == _owner || isApprovedForAll[_owner][caller], "Unapproved caller");
179+
require(balanceOf[_owner][_tokenId] >= _amount, "Not enough tokens owned");
180+
181+
_burn(_owner, _tokenId, _amount);
182+
}
183+
184+
/**
185+
* @notice Lets an owner or approved operator burn NFTs of the given tokenIds.
186+
*
187+
* @param _owner The owner of the NFTs to burn.
188+
* @param _tokenIds The tokenIds of the NFTs to burn.
189+
* @param _amounts The amounts of the NFTs to burn.
190+
*/
191+
function burnBatch(
192+
address _owner,
193+
uint256[] memory _tokenIds,
194+
uint256[] memory _amounts
195+
) external virtual {
196+
address caller = msg.sender;
197+
198+
require(caller == _owner || isApprovedForAll[_owner][caller], "Unapproved caller");
199+
require(_tokenIds.length == _amounts.length, "Length mismatch");
200+
201+
for (uint256 i = 0; i < _tokenIds.length; i += 1) {
202+
require(balanceOf[_owner][_tokenIds[i]] >= _amounts[i], "Not enough tokens owned");
203+
}
204+
205+
_burnBatch(_owner, _tokenIds, _amounts);
206+
}
207+
208+
/*//////////////////////////////////////////////////////////////
209+
ERC165 Logic
210+
//////////////////////////////////////////////////////////////*/
211+
212+
/// @notice Returns whether this contract supports the given interface.
213+
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155, IERC165) returns (bool) {
214+
return
215+
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
216+
interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
217+
interfaceId == 0x0e89341c || // ERC165 Interface ID for ERC1155MetadataURI
218+
interfaceId == type(IERC2981).interfaceId; // ERC165 ID for ERC2981
219+
}
220+
221+
/*//////////////////////////////////////////////////////////////
222+
View functions
223+
//////////////////////////////////////////////////////////////*/
224+
225+
/// @notice The tokenId assigned to the next new NFT to be minted.
226+
function nextTokenIdToMint() public view virtual returns (uint256) {
227+
return nextTokenIdToMint_;
228+
}
229+
230+
/*//////////////////////////////////////////////////////////////
231+
Internal (overrideable) functions
232+
//////////////////////////////////////////////////////////////*/
233+
234+
/// @dev Returns whether contract metadata can be set in the given execution context.
235+
function _canSetContractURI() internal view virtual override returns (bool) {
236+
return msg.sender == owner();
237+
}
238+
239+
/// @dev Returns whether a token can be minted in the given execution context.
240+
function _canMint() internal view virtual returns (bool) {
241+
return msg.sender == owner();
242+
}
243+
244+
/// @dev Returns whether owner can be set in the given execution context.
245+
function _canSetOwner() internal view virtual override returns (bool) {
246+
return msg.sender == owner();
247+
}
248+
249+
/// @dev Returns whether royalty info can be set in the given execution context.
250+
function _canSetRoyaltyInfo() internal view virtual override returns (bool) {
251+
return msg.sender == owner();
252+
}
253+
254+
/// @dev Runs before every token transfer / mint / burn.
255+
function _beforeTokenTransfer(
256+
address operator,
257+
address from,
258+
address to,
259+
uint256[] memory ids,
260+
uint256[] memory amounts,
261+
bytes memory data
262+
) internal virtual override {
263+
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
264+
265+
if (from == address(0)) {
266+
for (uint256 i = 0; i < ids.length; ++i) {
267+
totalSupply[ids[i]] += amounts[i];
268+
}
269+
}
270+
271+
if (to == address(0)) {
272+
for (uint256 i = 0; i < ids.length; ++i) {
273+
totalSupply[ids[i]] -= amounts[i];
274+
}
275+
}
276+
}
277+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "./ERC1155LazyMint.sol";
5+
import "../extension/DelayedReveal.sol";
6+
7+
/**
8+
* BASE: ERC1155Base
9+
* EXTENSION: LazyMint, DelayedReveal
10+
*
11+
* The `ERC1155DelayedReveal` contract uses the `ERC1155Base` contract, along with the `LazyMint` and `DelayedReveal` extension.
12+
*
13+
* 'Lazy minting' means defining the metadata of NFTs without minting it to an address. Regular 'minting'
14+
* of NFTs means actually assigning an owner to an NFT.
15+
*
16+
* As a contract admin, this lets you prepare the metadata for NFTs that will be minted by an external party,
17+
* without paying the gas cost for actually minting the NFTs.
18+
*
19+
* 'Delayed reveal' is a mechanism by which you can distribute NFTs to your audience and reveal the metadata of the distributed
20+
* NFTs, after the fact.
21+
*
22+
* You can read more about how the `DelayedReveal` extension works, here: https://blog.thirdweb.com/delayed-reveal-nfts
23+
*/
24+
25+
contract ERC1155DelayedReveal is ERC1155LazyMint, DelayedReveal {
26+
using TWStrings for uint256;
27+
28+
/*//////////////////////////////////////////////////////////////
29+
Constructor
30+
//////////////////////////////////////////////////////////////*/
31+
32+
constructor(
33+
string memory _name,
34+
string memory _symbol,
35+
address _royaltyRecipient,
36+
uint128 _royaltyBps
37+
) ERC1155LazyMint(_name, _symbol, _royaltyRecipient, _royaltyBps) {}
38+
39+
/*//////////////////////////////////////////////////////////////
40+
Overriden Metadata logic
41+
//////////////////////////////////////////////////////////////*/
42+
43+
/**
44+
* @notice Returns the metadata URI for an NFT.
45+
* @dev See `BatchMintMetadata` for handling of metadata in this contract.
46+
*
47+
* @param _tokenId The tokenId of an NFT.
48+
*/
49+
function uri(uint256 _tokenId) public view override returns (string memory) {
50+
(uint256 batchId, ) = getBatchId(_tokenId);
51+
string memory batchUri = getBaseURI(_tokenId);
52+
53+
if (isEncryptedBatch(batchId)) {
54+
return string(abi.encodePacked(batchUri, "0"));
55+
} else {
56+
return string(abi.encodePacked(batchUri, _tokenId.toString()));
57+
}
58+
}
59+
60+
/*//////////////////////////////////////////////////////////////
61+
Lazy minting logic
62+
//////////////////////////////////////////////////////////////*/
63+
64+
/**
65+
* @notice Lets an authorized address lazy mint a given amount of NFTs.
66+
*
67+
* @param _amount The number of NFTs to lazy mint.
68+
* @param _baseURIForTokens The placeholder base URI for the 'n' number of NFTs being lazy minted, where the
69+
* metadata for each of those NFTs is `${baseURIForTokens}/${tokenId}`.
70+
* @param _data The encrypted base URI + provenance hash for the batch of NFTs being lazy minted.
71+
* @return batchId A unique integer identifier for the batch of NFTs lazy minted together.
72+
*/
73+
function lazyMint(
74+
uint256 _amount,
75+
string calldata _baseURIForTokens,
76+
bytes calldata _data
77+
) public virtual override returns (uint256 batchId) {
78+
if (_data.length > 0) {
79+
(bytes memory encryptedURI, bytes32 provenanceHash) = abi.decode(_data, (bytes, bytes32));
80+
if (encryptedURI.length != 0 && provenanceHash != "") {
81+
_setEncryptedData(nextTokenIdToLazyMint + _amount, _data);
82+
}
83+
}
84+
85+
return super.lazyMint(_amount, _baseURIForTokens, _data);
86+
}
87+
88+
/*//////////////////////////////////////////////////////////////
89+
Delayed reveal logic
90+
//////////////////////////////////////////////////////////////*/
91+
92+
/**
93+
* @notice Lets an authorized address reveal a batch of delayed reveal NFTs.
94+
*
95+
* @param _index The ID for the batch of delayed-reveal NFTs to reveal.
96+
* @param _key The key with which the base URI for the relevant batch of NFTs was encrypted.
97+
*/
98+
function reveal(uint256 _index, bytes calldata _key) external virtual override returns (string memory revealedURI) {
99+
require(_canReveal(), "Not authorized");
100+
101+
uint256 batchId = getBatchIdAtIndex(_index);
102+
revealedURI = getRevealURI(batchId, _key);
103+
104+
_setEncryptedData(batchId, "");
105+
_setBaseURI(batchId, revealedURI);
106+
107+
emit TokenURIRevealed(_index, revealedURI);
108+
}
109+
110+
/// @dev Checks whether NFTs can be revealed in the given execution context.
111+
function _canReveal() internal view virtual returns (bool) {
112+
return msg.sender == owner();
113+
}
114+
}

0 commit comments

Comments
 (0)