/// @notice Maps nullifiers to user identifiers for registration tracking
mapping(uint256 nullifier => uint256 userIdentifier) internal _nullifierToUserIdentifier;
/// @notice Maps user identifiers to registration status
mapping(uint256 userIdentifier => bool registered) internal _registeredUserIdentifiers;
/// @notice Tracks addresses that have claimed tokens
mapping(address => bool) public claimed;
/// @notice ERC20 token to be airdropped
IERC20 public immutable token;
/// @notice Merkle root for claim validation
bytes32 public merkleRoot;
/// @notice Phase control
bool public isRegistrationOpen;
bool public isClaimOpen;
/// @notice Verification config ID for identity verification
bytes32 public verificationConfigId;
function customVerificationHook(
ISelfVerificationRoot.GenericDiscloseOutputV2 memory output,
bytes memory /* userData */
) internal override {
// Airdrop-specific validations
if (!isRegistrationOpen) revert RegistrationNotOpen();
if (_nullifierToUserIdentifier[output.nullifier] != 0) revert RegisteredNullifier();
if (output.userIdentifier == 0) revert InvalidUserIdentifier();
if (_registeredUserIdentifiers[output.userIdentifier]) revert UserIdentifierAlreadyRegistered();
// Register user for airdrop
_nullifierToUserIdentifier[output.nullifier] = output.userIdentifier;
_registeredUserIdentifiers[output.userIdentifier] = true;
emit UserIdentifierRegistered(output.userIdentifier, output.nullifier);
}
function claim(uint256 index, uint256 amount, bytes32[] memory merkleProof) external {
if (isRegistrationOpen) {
revert RegistrationNotClosed();
}
if (!isClaimOpen) {
revert ClaimNotOpen();
}
if (claimed[msg.sender]) {
revert AlreadyClaimed();
}
if (!_registeredUserIdentifiers[uint256(uint160(msg.sender))]) {
revert NotRegistered(msg.sender);
}
// Verify the Merkle proof
bytes32 node = keccak256(abi.encodePacked(index, msg.sender, amount));
if (!MerkleProof.verify(merkleProof, merkleRoot, node)) revert InvalidProof();
// Mark as claimed and transfer tokens
claimed[msg.sender] = true;
token.safeTransfer(msg.sender, amount);
emit Claimed(index, msg.sender, amount);
}
// Set verification config ID
function setConfigId(bytes32 configId) external onlyOwner {
verificationConfigId = configId;
}
// Override to provide configId for verification
function getConfigId(
bytes32 destinationChainId,
bytes32 userIdentifier,
bytes memory userDefinedData
) public view override returns (bytes32) {
return verificationConfigId;
}
// Set Merkle root for claim validation
function setMerkleRoot(bytes32 newMerkleRoot) external onlyOwner;
// Update verification scope
function setScope(uint256 newScope) external onlyOwner;
// Phase control
function openRegistration() external onlyOwner;
function closeRegistration() external onlyOwner;
function openClaim() external onlyOwner;
function closeClaim() external onlyOwner;