diff --git a/.gitignore b/.gitignore index 2228abd..1b9c631 100644 --- a/.gitignore +++ b/.gitignore @@ -56,4 +56,4 @@ docs/ .env # Sismo connect config -sismo-connect-config.json \ No newline at end of file +sismo-connect-config.json diff --git a/abi/Airdrop.json b/abi/Airdrop.json index 7e07132..47a1b58 100644 --- a/abi/Airdrop.json +++ b/abi/Airdrop.json @@ -146,6 +146,109 @@ "name": "Approval", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "enum AuthType", + "name": "authType", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "isAnon", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "userId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proofData", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct VerifiedAuth", + "name": "verifiedAuth", + "type": "tuple" + } + ], + "name": "AuthVerified", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "enum ClaimType", + "name": "claimType", + "type": "uint8" + }, + { + "internalType": "bytes16", + "name": "groupId", + "type": "bytes16" + }, + { + "internalType": "bytes16", + "name": "groupTimestamp", + "type": "bytes16" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "proofId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "proofData", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct VerifiedClaim", + "name": "verifiedClaim", + "type": "tuple" + } + ], + "name": "ClaimVerified", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "verifiedSignedMessage", + "type": "bytes" + } + ], + "name": "SignedMessageVerified", + "type": "event" + }, { "anonymous": false, "inputs": [ diff --git a/front/src/app/components/Header.tsx b/front/src/app/components/Header.tsx index 0abd873..e373736 100644 --- a/front/src/app/components/Header.tsx +++ b/front/src/app/components/Header.tsx @@ -33,6 +33,15 @@ const Header: React.FC = () => { src/Airdrop.sol: Contract - verify Sismo Connect request, mint tokens and stores verified claims and auths

+

+ {" "} + Notes:
+ 1. If you are using metamask and transactions hang. Go to settings > advanced > clear activity and nonce data
+ 2. First ZK Proof generation takes longer time, especially with bad internet as there is a + zkey file to download once in the data vault connection
+ 3. The more proofs you request, the longer it takes to generate them (about 2 secs per + proof) +

); diff --git a/front/src/app/globals.css b/front/src/app/globals.css index 9707953..66f1a08 100644 --- a/front/src/app/globals.css +++ b/front/src/app/globals.css @@ -158,11 +158,12 @@ input:focus { .callout { - color: #d3d3d3; /* Light gray color */ + color: #000000; /* Black color text */ padding: 1rem; /* Adds some space around the text */ - border: 1px solid #d3d3d3; /* Adds a light gray border */ + border: 2px solid #000000; /* Increases border thickness and change to black */ border-radius: 5px; /* Rounds the corners of the border */ - background-color: rgba(0,0,0,0.1); /* Adds a very light black background to increase readability of gray text */ + background-color: rgba(255,255,255,0.6); /* Adds a semi-transparent white background to increase readability of black text */ + box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); /* Adds a slight shadow for a "lifted" effect */ } .verifying, .verified { diff --git a/front/src/app/page.tsx b/front/src/app/page.tsx index ea7b384..b6806ee 100644 --- a/front/src/app/page.tsx +++ b/front/src/app/page.tsx @@ -60,7 +60,7 @@ export default function Home() { <> <> {" "} - + {pageState != "init" && }

{`Chain: ${chain?.name} [${chain?.id}]`}
@@ -71,30 +71,22 @@ export default function Home() { <> { setResponseBytes(responseBytes); }} // Some text to display on the button text={"Claim with Sismo"} /> -

- {" "} - Notes:
- 1. First ZK Proof generation takes longer time, especially with bad internat as - there is a zkey file to download once in the data vault connection
- 2. The more proofs you request, the longer it takes to generate them (about 2 secs - per proof) -

)}
diff --git a/front/src/app/sismo-connect-config.ts b/front/src/app/sismo-connect-config.ts index 65614b1..1e5afa8 100644 --- a/front/src/app/sismo-connect-config.ts +++ b/front/src/app/sismo-connect-config.ts @@ -35,6 +35,7 @@ export const CONFIG: SismoConnectConfig = { // Sismo Connect Response in the vault instead of redirecting back to the app }; +// Request users to prove ownership of a Data Source (Wallet, Twitter, Github, Telegram, etc.) export const AUTHS: AuthRequest[] = [ // Anonymous identifier of the vault for this app // vaultId = hash(vaultSecret, appId). @@ -46,6 +47,7 @@ export const AUTHS: AuthRequest[] = [ // { authType: AuthType.TELEGRAM, userId: "875608110", isOptional: true }, ]; +// Request users to prove membership in a Data Group (e.g I own a wallet that is part of a DAO, owns an NFT, etc.) export const CLAIMS: ClaimRequest[] = [ { // claim on Sismo Hub GitHub Contributors Data Group membership: https://factory.sismo.io/groups-explorer?search=0xda1c3726426d5639f4c6352c2c976b87 @@ -71,11 +73,12 @@ export const CLAIMS: ClaimRequest[] = [ // request user to prove membership in the group with value = 10 groupId: "0xfae674b6cba3ff2f8ce2114defb200b1", claimType: ClaimType.EQ, - value: 10, // dhadrin.sismo.eth minted exactly 10, eligible + value: 10, // dhadrien.sismo.eth minted exactly 10, eligible isOptional: true, }, ]; +// Request users to sign a message export const SIGNATURE_REQUEST: SignatureRequest = { message: "I love Sismo!", isSelectableByUser: true, diff --git a/front/src/utils/useContract.tsx b/front/src/utils/useContract.tsx index 20b10d9..bb6777f 100644 --- a/front/src/utils/useContract.tsx +++ b/front/src/utils/useContract.tsx @@ -84,15 +84,14 @@ export default function useContract({ let txReceipt: TransactionReceipt | undefined; if (chain.id === 5151111) { const timeout = new Promise((_, reject) => - setTimeout( - () => - reject( - new Error( - "Local fork error: operation timed out after 15 seconds, if you are running a local fork on Anvil please make sure to reset your wallet nonce." - ) - ), - 10000 - ) + setTimeout(() => { + setPageState("responseReceived"); + reject( + new Error( + "Transaction timed-out: If you are running a local fork on Anvil please make sure to reset your wallet nonce. In metamask: Go to settings > advanced > clear activity and nonce data" + ) + ); + }, 10000) ); const txReceiptPromise = tx && waitForTransaction({ hash: tx.hash }); const race = await Promise.race([txReceiptPromise, timeout]); diff --git a/src/Airdrop.sol b/src/Airdrop.sol index 6711e5d..2cd593d 100644 --- a/src/Airdrop.sol +++ b/src/Airdrop.sol @@ -3,23 +3,30 @@ pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "forge-std/console.sol"; -import "sismo-connect-solidity/SismoLib.sol"; // <--- add a Sismo Connect import +import "sismo-connect-solidity/SismoLib.sol"; /* * @title Airdrop * @author Sismo - * @dev Simple Airdrop contract that mints ERC20 tokens to the msg.sender - * This contract is used for tutorial purposes only - * It will be used to demonstrate how to integrate Sismo Connect + * @dev Simple Airdrop contract gated by Sismo Connect + * Application requests multiple zk proofs (auths and claims) and verify them + * The contract stores all verified results in storage */ contract Airdrop is ERC20, SismoConnect { error AlreadyClaimed(); + event AuthVerified(VerifiedAuth verifiedAuth); + event ClaimVerified(VerifiedClaim verifiedClaim); + event SignedMessageVerified(bytes verifiedSignedMessage); using SismoConnectHelper for SismoConnectVerifiedResult; mapping(uint256 => bool) public claimed; + // must correspond to requests defined in the app frontend + // Sismo Connect response's zk proofs will be checked against these requests. + // check Airdrop.s.sol to see how these requests are built and passed to the constructor AuthRequest[] private _authRequests; ClaimRequest[] private _claimRequests; + // Results of the verification of the Sismo Connect response. VerifiedAuth[] internal _verifiedAuths; VerifiedClaim[] internal _verifiedClaims; bytes internal _verifiedSignedMessage; @@ -36,66 +43,48 @@ contract Airdrop is ERC20, SismoConnect { _setClaims(claimRequests); } -// struct SismoConnectVerifiedResult { -// bytes16 appId; -// bytes16 namespace; -// bytes32 version; -// VerifiedAuth[] auths; -// VerifiedClaim[] claims; -// bytes signedMessage; -// } - function claimWithSismo(bytes memory response) public { SismoConnectVerifiedResult memory result = verify({ responseBytes: response, - // we want the user to prove that he owns a Sismo Vault - // we are recreating the auth request made in the frontend to be sure that - // the proofs provided in the response are valid with respect to this auth request + // checking response against requested auths auths: _authRequests, + // checking response against requested claims claims: _claimRequests, - // we also want to check if the signed message provided in the response is the signature of the user's address + // checking response against requested message signature signature: buildSignature({message: abi.encode(msg.sender)}) }); - for (uint256 i = 0; i < result.auths.length; i++) { - _verifiedAuths.push(result.auths[i]); - } - for (uint256 i = 0; i < result.claims.length; i++) { - _verifiedClaims.push(result.claims[i]); - } - - _verifiedSignedMessage =result.signedMessage; - - // if the proofs and signed message are valid, we take the userId from the verified result - // in this case the userId is the vaultId (since we used AuthType.VAULT in the auth request), // it is the anonymous identifier of a user's vault for a specific app // --> vaultId = hash(userVaultSecret, appId) + // used to avoid double claims uint256 vaultId = result.getUserId(AuthType.VAULT); - // we check if the user has already claimed the airdrop - // if (claimed[vaultId]) { - // revert AlreadyClaimed(); - // } + // checking if the user has already claimed + if (claimed[vaultId]) { + revert AlreadyClaimed(); + } - // we mark the user as claimed. We could also have stored more user airdrop information for a more complex airdrop system. But we keep it simple here. + // marking that the user has claimed claimed[vaultId] = true; + // airdrop amount = number of verified proofs uint256 airdropAmount = (result.auths.length + result.claims.length) * 10 ** 18; _mint(msg.sender, airdropAmount); - } - function _setAuths(AuthRequest[] memory auths) private { - for (uint256 i = 0; i < auths.length; i++) { - _authRequests.push(auths[i]); + // storing the result of the verification + for (uint256 i = 0; i < result.auths.length; i++) { + _verifiedAuths.push(result.auths[i]); + emit AuthVerified(result.auths[i]); } - } - - function _setClaims(ClaimRequest[] memory claims) private { - for (uint256 i = 0; i < claims.length; i++) { - _claimRequests.push(claims[i]); + for (uint256 i = 0; i < result.claims.length; i++) { + _verifiedClaims.push(result.claims[i]); + emit ClaimVerified(result.claims[i]); } + _verifiedSignedMessage =result.signedMessage; + emit SignedMessageVerified(result.signedMessage); } + function getVerifiedClaims() external view returns (VerifiedClaim[] memory) { return _verifiedClaims; } @@ -107,4 +96,17 @@ contract Airdrop is ERC20, SismoConnect { function getVerifiedSignedMessage() external view returns (bytes memory) { return _verifiedSignedMessage; } + + function _setAuths(AuthRequest[] memory auths) private { + for (uint256 i = 0; i < auths.length; i++) { + _authRequests.push(auths[i]); + } + } + + function _setClaims(ClaimRequest[] memory claims) private { + for (uint256 i = 0; i < claims.length; i++) { + _claimRequests.push(claims[i]); + } + } + }