There are some more modern alternatives which are essential to consider now that gas is so much higher.
Merkle root based whitelist (low cost, moderate flexibility)
Use all of the addresses on your list to generate a Merkle tree. Then have the user submit their proof (can serve on front-end, not sensitive since only the address holder can use their proof), along with the mint call.
I consider this medium flexibility because if you want to change the list at all you have to update the root on chain.
Generation code (js)
const newMerkle = new MerkleTree(
['0x1...', '0x2...'].map((token: string) => hashToken(token)),
keccak256,
{
duplicateOdd: false,
hashLeaves: false,
isBitcoinTree: false,
sortLeaves: false,
sortPairs: true,
sort: false,
}
)
export function hashToken(account: string) {
return Buffer.from(ethers.utils.solidityKeccak256(["address"], [account]).slice(2), "hex");
}
Verification code (solidity)
function mintPresale(uint256 _quantity, bytes32[] calldata _proof)
external
payable
{
require(_verify(_leaf(msg.sender), _proof), "Invalid merkle proof");
_mint(_quantity, msg.sender);
}
function _verify(bytes32 leaf_, bytes32[] memory _proof)
internal
view
returns (bool)
{
return MerkleProof.verify(_proof, root, leaf_);
}
This snippet uses code from OpenZeppelin Merkle Proof library and merkletreejs
Signature based whitelist (minimal cost, highly flexible)
Generate signatures ahead of time or live on a back end and serve them to the user to include in their mint call. These are also not sensitive so they can be served on the front end without any auth.
Signing code (typescript)
export default async function signWhitelist(
chainId: number,
contractAddress: string,
whitelistKey: SignerWithAddress,
mintingAddress: string,
nonce: number
) {
const domain = {
name: '[YOUR_CONTRACT_NAME}',
version: '1',
chainId,
verifyingContract: contractAddress,
}
const types = {
Minter: [
{ name: 'wallet', type: 'address' },
{ name: 'nonce', type: 'uint256' },
],
}
const sig = await whitelistKey._signTypedData(domain, types, {
wallet: mintingAddress,
nonce,
})
return sig
}
Verifying code (Solidity)
modifier requiresWhitelist(
bytes calldata signature,
uint256 nonce
) {
// Verify EIP-712 signature by recreating the data structure
// that we signed on the client side, and then using that to recover
// the address that signed the signature for this data.
bytes32 structHash = keccak256(
abi.encode(MINTER_TYPEHASH, msg.sender, nonce)
);
bytes32 digest = toTypedMessageHash(structHash); /*Calculate EIP712 digest*/
require(!signatureUsed[digest], "signature used");
signatureUsed[digest] = true;
// Use the recover method to see what address was used to create
// the signature on this data.
// Note that if the digest doesn't exactly match what was signed we'll
// get a random recovered address.
address recoveredAddress = digest.recover(signature);
require(
hasRole(WHITELISTING_ROLE, recoveredAddress),
"Invalid Signature"
);
_;
}
These snippets are adapted from this repo: https://github.com/msfeldstein/EIP712-whitelisting/blob/main/contracts/EIP712Whitelisting.sol
Transaction cost: 20784 gas, as opposed to theTransaction cost: 43464 gas, as you stated. I am looking at Gas Estimates -> External -> whitelistAddress(address). Is there another place that the gas is estimated? – blockchaindotsol Aug 09 '17 at 00:22