Let’s take a closer look at how to integrate our smart contract by using a concrete example. In this case, we will demonstrate how to integrate our Airdrop contract.
Importing Required Files
In the Airdrop contract, we override the verifySelfProof function to add custom functionality. For this purpose, import the following files:
import {SelfVerificationRoot} from "../abstract/SelfVerificationRoot.sol";
import {IVcAndDiscloseCircuitVerifier} from "../interfaces/IVcAndDiscloseCircuitVerifier.sol";
import {IIdentityVerificationHubV1} from "../interfaces/IIdentityVerificationHubV1.sol";
import {CircuitConstants} from "../constants/CircuitConstants.sol";
Verification Requirements for the Airdrop
For the Airdrop use case, the following verification checks are required:
Scope Verification:
Confirm that the proof was generated specifically for the Airdrop application by checking the scope used in the proof.
Attestation ID Verification:
Ensure the proof was generated using the correct attestation ID corresponding to the document type intended for the Airdrop.
Nullifier Registration and Verification:
Prevent double claims by registering and verifying a nullifier. Although the nullifier does not reveal any document details, it is uniquely tied to the document.
User Identifier Registration:
Verify that the proof includes a valid user identifier (in this case, the address that will receive the Airdrop).
Proof Verification by IdentityVerificationHub:
Validate the proof itself using our IdentityVerificationHub, which also performs additional checks (e.g., olderThan, forbiddenCountries, and OFAC validations) as configured for the Airdrop.
State Variables for Nullifier and User Identifier
Within the Airdrop contract, mappings are declared to keep track of used nullifiers and registered user identifiers:
The verifySelfProof function is overridden as follows to include all necessary checks:
function verifySelfProof(
IVcAndDiscloseCircuitVerifier.VcAndDiscloseProof memory proof
)
public
override
{
// Check that the registration period for the user identifier is open
if (!isRegistrationOpen) {
revert RegistrationNotOpen();
}
// Verify that the proof was generated using the correct scope
if (_scope != proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_SCOPE_INDEX]) {
revert InvalidScope();
}
// Verify that the proof was generated using the correct attestation ID
if (_attestationId != proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX]) {
revert InvalidAttestationId();
}
// Ensure the nullifier has not already been used
if (_nullifiers[proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_NULLIFIER_INDEX]] != 0) {
revert RegisteredNullifier();
}
// Verify that the proof includes a valid user identifier (i.e., the receiving address for the Airdrop)
if (proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX] == 0) {
revert InvalidUserIdentifier();
}
// Validate the proof itself. This includes checks for olderThan, forbiddenCountries, and OFAC settings as specified by the Airdrop contract.
IIdentityVerificationHubV1.VcAndDiscloseVerificationResult memory result = _identityVerificationHub.verifyVcAndDisclose(
IIdentityVerificationHubV1.VcAndDiscloseHubProof({
olderThanEnabled: _verificationConfig.olderThanEnabled,
olderThan: _verificationConfig.olderThan,
forbiddenCountriesEnabled: _verificationConfig.forbiddenCountriesEnabled,
forbiddenCountriesListPacked: _verificationConfig.forbiddenCountriesListPacked,
ofacEnabled: _verificationConfig.ofacEnabled,
vcAndDiscloseProof: proof
})
);
// Register the nullifier and the user identifier (i.e., the receiving address for the Airdrop)
_nullifiers[result.nullifier] = proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX];
_registeredUserIdentifiers[proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX]] = true;
// Emit an event to signal successful registration
emit UserIdentifierRegistered(
proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX],
result.nullifier
);
}
Configuring Verification Parameters
To allow modifications to the verification parameters—such as olderThan, forbiddenCountries, and ofac settings—the Airdrop contract imports the following file:
import {ISelfVerificationRoot} from "../interfaces/ISelfVerificationRoot.sol";
It then adds a function to update the verification configuration:
By following this example, you can see how our smart contract is integrated into an Airdrop scenario, ensuring that all necessary verifications are performed and that users receive Airdrops only under valid conditions.