Skip to content

Commit ac38e51

Browse files
Krishang NadgaudaKrishang Nadgauda
authored andcommitted
SignatureDrop design doc
1 parent bb3f0ae commit ac38e51

File tree

2 files changed

+203
-0
lines changed

2 files changed

+203
-0
lines changed

assets/signature-drop-diag.png

541 KB
Loading
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# SignatureDrop design document.
2+
3+
This is a live document that explains what the [thirdweb](https://thirdweb.com/) `SignatureDrop` smart contract is, how it works and can be used, and why it is designed the way it is.
4+
5+
The document is written for technical and non-technical readers. To ask further questions about thirdweb’s `SignatureDrop` contract, please join the [thirdweb discord](https://discord.gg/thirdweb) or create a github issue.
6+
7+
---
8+
9+
## Background
10+
11+
The thirdweb [`Drop`](https://portal.thirdweb.com/contracts/design/Drop) contracts are distribution mechanisms for tokens.
12+
13+
The `Drop` contracts are meant to be used when the goal of the contract creator is for an audience to come in and claim tokens within certain restrictions e.g. — ‘only addresses in an allowlist can mint tokens’, or ‘minters must pay **x** amount of price in **y** currency to mint’, etc.
14+
15+
The `Drop` contracts let the contract creator establish phases (periods of time), where each phase can specify multiple such restrictions on the minting of tokens during that period of time. We refer to such a phase as a ‘claim condition’.
16+
17+
`SignatureDrop` adds on to the functionality by allowing the audience to claim tokens using a mint-request pre-signed by the contract creator.
18+
19+
### How the `SignatureDrop` product *should* work
20+
21+
![signature-drop-diag.png](/assets/signature-drop-diag.png)
22+
23+
### Core parts of the `SignatureDrop` product
24+
25+
- An authorized account should be able to batch upload NFT metadata, and set whether the base URI is encrypted or not.
26+
- An authorized account should be able to authorize an external party to claim tokens on their contract using a signed mint-request.
27+
- An account with the admin role should be able to set claim conditions
28+
- Allowlisted accounts should be able to claim tokens within a particular claim condition when active.
29+
30+
### Why we’re building `SignatureDrop`
31+
32+
We built our `Drop` contracts for the following [reason](https://portal.thirdweb.com/contracts/design/Drop#why-were-building-drop). The limitation of our `Drop` contracts is that all wallets in an audience attempting to claim tokens are subject to the same restrictions in the single, active claim phase at any moment.
33+
34+
In the `SignatureDrop` contract, a wallet can now claim tokens [via a signature](https://portal.thirdweb.com/contracts/design/SignatureMint#background) from an authorized wallet, from the same pool of lazy minted tokens which can be claimed via the `Drop` mechanism. This means a contract owner can now set custom restrictions for a wallet to claim tokens, that may be different from the active claim phase at the time.
35+
36+
## Technical Details
37+
38+
`SignatureDrop` is an ERC721 contract.
39+
40+
A contract admin can lazy mint tokens, and establish phases for an audience to come claim those tokens under the restrictions of the active phase at the time. On a per wallet basis, a contract admin can let a wallet claim those tokens under restrictions different than the active claim phase, via signature minting.
41+
42+
### Batch upload of NFTs metadata: LazyMint
43+
44+
The contract creator or an address with MINTER_ROLE mints *n* NFTs, by providing base URI for the tokens or an encrypted URI.
45+
```solidity
46+
function lazyMint(
47+
uint256 _amount,
48+
string calldata _baseURIForTokens,
49+
bytes calldata _encryptedBaseURI
50+
) external onlyRole(MINTER_ROLE) returns (uint256 batchId)
51+
```
52+
| Parameters | Type | Description |
53+
| --- | --- | --- |
54+
| _amount | uint256 | Amount of tokens to lazy-mint. |
55+
| _baseURIForTokens | string | The metadata URI for the batch of tokens. |
56+
| _encryptedBaseURI | bytes | Encrypted URI for the batch of tokens. |
57+
58+
### Delayed reveal
59+
60+
An account with MINTER_ROLE can reveal the URI for a batch of ‘delayed-reveal’ NFTs. The URI can be revealed by calling the following function:
61+
```solidity
62+
function reveal(uint256 _index, bytes calldata _key)
63+
external
64+
onlyRole(MINTER_ROLE)
65+
returns (string memory revealedURI)
66+
```
67+
| Parameters | Type | Description |
68+
| --- | --- | --- |
69+
| _index | uint256 | Index of the batch for which URI is to be revealed. |
70+
| _key | bytes | Key for decrypting the URI. |
71+
72+
### Claiming tokens via signature
73+
74+
An account with MINTER_ROLE signs the mint request for a user. The mint request is then submitted for claiming the tokens. The mint request is specified in the following format:
75+
```solidity
76+
struct MintRequest {
77+
address to;
78+
address royaltyRecipient;
79+
uint256 royaltyBps;
80+
address primarySaleRecipient;
81+
string uri;
82+
uint256 quantity;
83+
uint256 pricePerToken;
84+
address currency;
85+
uint128 validityStartTimestamp;
86+
uint128 validityEndTimestamp;
87+
bytes32 uid;
88+
}
89+
```
90+
| Parameters | Type | Description |
91+
| --- | --- | --- |
92+
| to | address | The recipient of the tokens to mint. |
93+
| royaltyRecipient | address | The recipient of the minted token's secondary sales royalties. |
94+
| royaltyBps | uint256 | The percentage of the minted token's secondary sales to take as royalties. |
95+
| primarySaleRecipient | address | The recipient of the minted token's primary sales proceeds. |
96+
| uri | string | The metadata URI of the token to mint. |
97+
| quantity | uint256 | The quantity of tokens to mint. |
98+
| pricePerToken | uint256 | The price to pay per quantity of tokens minted. |
99+
| currency | address | The currency in which to pay the price per token minted. |
100+
| validityStartTimestamp | uint128 | The unix timestamp after which the payload is valid. |
101+
| validityEndTimestamp | uint128 | The unix timestamp at which the payload expires. |
102+
| uid | bytes32 | A unique identifier for the payload. |
103+
104+
The authorized external party can mint the tokens by submitting mint-request and contract owner’s signature to the following function:
105+
```solidity
106+
function mintWithSignature(
107+
ISignatureMintERC721.MintRequest calldata _req,
108+
bytes calldata _signature
109+
) external payable
110+
```
111+
| Parameters | Type | Description |
112+
| --- | --- | --- |
113+
| _req | ISignatureMintERC721.MintRequest | Mint request in the format specified above. |
114+
| _signature | bytes | Contact owner’s signature for the mint request. |
115+
116+
### Setting claim conditions and regular claiming of tokens
117+
118+
A contract admin (i.e. a holder of `DEFAULT_ADMIN_ROLE`) can set a series of claim conditions, ordered by their respective `startTimestamp`. A claim condition defines criteria under which accounts can mint tokens. Claim conditions can be overwritten, or added to, by the contract admin. At any moment, there is only one active claim condition.
119+
120+
A claim condition is specified in the following format:
121+
```solidity
122+
struct ClaimCondition {
123+
uint256 startTimestamp;
124+
uint256 maxClaimableSupply;
125+
uint256 supplyClaimed;
126+
uint256 quantityLimitPerTransaction;
127+
uint256 waitTimeInSecondsBetweenClaims;
128+
bytes32 merkleRoot;
129+
uint256 pricePerToken;
130+
address currency;
131+
}
132+
```
133+
| Parameters | Type | Description |
134+
| --- | --- | --- |
135+
| startTimestamp | uint256 | The unix timestamp after which the claim condition applies. The same claim condition applies until the startTimestamp of the next claim condition. |
136+
| maxClaimableSupply | uint256 | The maximum total number of tokens that can be claimed under the claim condition. |
137+
| supplyClaimed | uint256 | At any given point, the number of tokens that have been claimed under the claim condition. |
138+
| quantityLimitPerTransaction | uint256 | The maximum number of tokens that can be claimed in a single transaction. |
139+
| waitTimeInSecondsBetweenClaims | uint256 | The least number of seconds an account must wait after claiming tokens, to be able to claim tokens again.. |
140+
| merkleRoot | bytes32 | The allowlist of addresses that can claim tokens under the claim condition. |
141+
| pricePerToken | uint256 | The price required to pay per token claimed. |
142+
| currency | address | The currency in which the pricePerToken must be paid. |
143+
144+
The set of all claim conditions is stored in the following format:
145+
```solidity
146+
struct ClaimConditionList {
147+
uint256 currentStartId;
148+
uint256 count;
149+
mapping(uint256 => ClaimCondition) conditions;
150+
mapping(uint256 => mapping(address => uint256)) lastClaimTimestamp;
151+
mapping(uint256 => BitMapsUpgradeable.BitMap) usedAllowlistSpot;
152+
}
153+
```
154+
| Parameters | Type | Description |
155+
| --- | --- | --- |
156+
| currentStartId | uint256 | The uid for the first claim condition amongst the current set of claim conditions. The uid for each next claim condition is one more than the previous claim condition's uid. |
157+
| count | uint256 | The total number of phases / claim conditions in the list of claim conditions. |
158+
| conditions | mapping(uint256 => ClaimCondition) | The claim conditions at a given uid. Claim conditions are ordered in an ascending order by their startTimestamp. |
159+
| lastClaimTimestamp | mapping(uint256 => mapping(address => uint256)) | Map from an account and uid for a claim condition, to the last timestamp at which the account claimed tokens under that claim condition. |
160+
| usedAllowlistSpot | mapping(uint256 => BitMapsUpgradeable.BitMap) | Map from a claim condition uid to whether an address in an allowlist has already claimed tokens i.e. used their place in the allowlist. |
161+
162+
An account can claim the tokens by calling the following function:
163+
```solidity
164+
function claim(
165+
address _receiver,
166+
uint256 _quantity,
167+
address _currency,
168+
uint256 _pricePerToken,
169+
AllowlistProof calldata _allowlistProof,
170+
bytes memory _data
171+
) public payable;
172+
```
173+
| Parameters | Type | Description |
174+
| --- | --- | --- |
175+
| _receiver | address | Mint request in the format specified above. |
176+
| _quantity | uint256 | Contact owner’s signature for the mint request. |
177+
| _currency | address | The currency in which the price must be paid. |
178+
| _pricePerToken | uint256 | The price required to pay per token claimed. |
179+
| _allowlistProof | AllowlistProof | The proof of the claimer's inclusion in the merkle root allowlist of the claim conditions that apply. |
180+
| _data | bytes | Arbitrary bytes data that can be leveraged in the implementation of this interface. |
181+
182+
## Permissions
183+
184+
| Role name | Type (Switch / !Switch) | Purpose |
185+
| -- | -- | -- |
186+
| TRANSFER_ROLE | Switch | Only token transfers to or from role holders are allowed. |
187+
| MINTER_ROLE | !Switch | Only MINTER_ROLE holders can sign off on MintRequests and lazy mint tokens. |
188+
189+
What does **Type (Switch / !Switch)** mean?
190+
- **Switch:** If `address(0)` has `ROLE`, then the `ROLE` restrictions don't apply.
191+
- **!Switch:** `ROLE` restrictions always apply.
192+
193+
## Relevant EIPs
194+
195+
| EIP | Link | Relation to SignatureDrop |
196+
| --- | --- | --- |
197+
| 721 | https://eips.ethereum.org/EIPS/eip-721 | `SignatureDrop` is an ERC721 contract. |
198+
| 2981 | https://eips.ethereum.org/EIPS/eip-2981 | `SignatureDrop` implements ERC 2981 for distributing royalties for sales of the wrapped NFTs. |
199+
| 2771 | https://eips.ethereum.org/EIPS/eip-2771 | `SignatureDrop` implements ERC 2771 to support meta-transactions (aka “gasless” transactions). |
200+
201+
## Authors
202+
- [kumaryash90](https://github.com/kumaryash90)
203+
- [thirdweb team](https://github.com/thirdweb-dev)

0 commit comments

Comments
 (0)