generated from Consensys/doctools.template-site
-
Notifications
You must be signed in to change notification settings - Fork 541
Proof of Humanity V2 tutorial #1251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Gwen-M
wants to merge
1
commit into
Consensys:main
Choose a base branch
from
Gwen-M:poh-tutorial
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,355 @@ | ||
| --- | ||
| title: Proof of Humanity tutorial | ||
| description: Learn how to verify users with Proof of Humanity V2 on Linea. | ||
| image: /img/socialCards/verify-users-with-proof-of-humanity.jpg | ||
| --- | ||
|
|
||
| Proof of Humanity (PoH) lets you verify whether a wallet belongs to a real human on Linea. It | ||
| provides a simple and reliable way for projects to gate access to rewards or features exclusively for | ||
| verified users. | ||
|
|
||
| PoH V2 is powered by a single attestation issued by Sumsub through the Verax Attestation Registry. | ||
| This version replaces the (too) complex multi-provider setup that was used for PoH V1, simplifying | ||
| verification to a single, trusted data source. | ||
|
|
||
| ## The Sybil problem | ||
|
|
||
| In Web3, identity is fluid, meaning users can create as many wallets as they want. This openness | ||
| fuels innovation but also creates opportunities for abuse. A Sybil attack occurs when one actor | ||
| generates multiple fake identities to exploit a system designed for fair participation. | ||
|
|
||
| Common examples include: | ||
|
|
||
| - Airdrop farming, where a single individual controls thousands of wallets to claim rewards. | ||
| - Fake voting in DAOs or community governance, undermining collective decisions. | ||
| - Spam campaigns that flood onchain communities or social apps. | ||
| - Automated account creation to game loyalty programs or incentive systems. | ||
|
|
||
| Recent data shows how widespread this problem has become. Nansen and other web3 analytics firms | ||
| have revealed that a significant share of wallet activity in certain programs comes from non-human | ||
| or clustered entities. For instance, several major projects recently had to filter tens of millions | ||
| of wallets to preserve fairness in their distributions (e.g. Linea had to filter more than half a | ||
| million wallets to preserve our TGE from farmers). | ||
|
|
||
| The takeaway is clear: without human verification, decentralized systems remain vulnerable. Linea's | ||
| Proof of Humanity V2 addresses this by providing developers a reliable way to ensure "one human, | ||
| one action." | ||
|
|
||
| ## Why verifying users matters | ||
|
|
||
| Verifying users isn't about restricting access - it's about building integrity into digital | ||
| communities and economies. | ||
|
|
||
| For developers, it means: | ||
|
|
||
| - Preventing fraud and abuse by excluding automated or duplicate accounts. Bots inflate all your | ||
| metrics, giving you misleading data about your app growth and you end up optimizing for noise, not | ||
| real adoption. | ||
| - Protecting incentive systems like governance tokens, loyalty points, or rewards programs. Bots can | ||
| quickly drain incentives systems without adding value, PoH helps you target your spending to real | ||
| users only. | ||
| - Reducing moderation costs by ensuring real humans drive community engagement. | ||
|
|
||
| For users, it builds: | ||
|
|
||
| - Trust and fairness, knowing that everyone participates on equal footing. | ||
| - Safety, by reducing exposure to spam, scams, or bots. | ||
| - Reputation portability, since a verified identity can be reused across multiple dapps within the | ||
| Linea ecosystem. | ||
|
|
||
| Verification also enables new categories of applications — from proof-of-personhood voting to | ||
| verified social reputation, identity-based access control, or even credit scoring. | ||
|
|
||
| ## Unlocking growth through verification | ||
|
|
||
| Human verification isn't just a security measure — it's a growth accelerator. | ||
|
|
||
| When users trust that interactions are genuine, engagement and retention improve. Developers can | ||
| focus on building features instead of moderating spam or chasing fraudulent claims. Communities | ||
| become healthier, and token incentives circulate among real participants. | ||
|
|
||
| With PoH V2, Linea dapps can: | ||
|
|
||
| - Simplify onboarding with a single verification flow across the ecosystem. | ||
| - Enhance brand credibility by signaling transparency and integrity. | ||
| - Foster sustainable ecosystems, where every wallet represents a verified human being. This will | ||
| fuel every human-to-human interaction, knowing bots won't have a seat at the table. | ||
|
|
||
| Trust, in this context, becomes a competitive advantage, and a key differentiator for web3 | ||
| applications aiming to scale responsibly. | ||
|
|
||
| ## Verify: how users complete their Proof of Humanity | ||
|
|
||
| You can integrate Proof of Humanity in your app to restrict access, rewards or actions to real users | ||
| only. Use the **Sumsub Proof of Personhood flow** to allow users to verify their humanity directly | ||
| from your app. This flow handles user verification through **Sumsub's Proof of Personhood** flow and | ||
| issues a **Verax attestation** onchain. | ||
|
|
||
| :::tip Before you start | ||
| You'll need: | ||
|
|
||
| - A frontend app connected to the Linea network | ||
| - A reliable Linea Mainnet RPC provider such as [Infura](https://developer.metamask.io) | ||
| - A way to read the user's wallet address (e.g. [Wagmi](https://wagmi.sh/react/api/hooks/useAccount)) | ||
| ::: | ||
|
|
||
| ### Typical flow | ||
|
|
||
| Here's how the complete user verification process works step-by-step: | ||
|
|
||
| 1. The user connects their Ethereum wallet. | ||
| 2. Your app queries the Linea PoH API to check whether the user is already verified. Endpoint: | ||
| `https://poh-api.linea.build/poh/v2/{address}` Documentation | ||
| [here](https://poh-api.linea.build/#/PoH/PohController_getOnePOHv2). | ||
|
|
||
| The endpoint returns `true` if the address is verified, or `false` otherwise. Use `false` to trigger | ||
| the Sumsub flow (next step). | ||
|
|
||
| 3. Your app asks them to sign a message, containing 2 variables: | ||
|
|
||
| - Their wallet address | ||
| - A timestamp (ISO date) | ||
|
|
||
| The signature is used by Sumsub to confirm wallet ownership and link the verification to the correct | ||
| address onchain. A recommended way to sign the message is to use Wagmi's | ||
| [`useSignMessage` hook](https://wagmi.sh/react/api/hooks/useSignMessage): | ||
|
|
||
| ```tsx | ||
| import {useAccount, useSignMessage} from "wagmi"; | ||
|
|
||
| function App() { | ||
| const { address } = useAccount(); | ||
| const { signMessage } = useSignMessage(); | ||
|
|
||
| const date = new Date().toISOString(); | ||
|
|
||
| return ( | ||
| <button onClick={() => signMessage({ | ||
| message: `in.sumsub.com wants you to sign in with your Ethereum account:\n${address}\n\nI confirm that I am the owner of this wallet and consent to performing a risk assessment and issuing a Verax attestation to this address.\n\nURI: https://in.sumsub.com\nVersion: 1\nChain ID: 59144\nIssued At: ${date}`, | ||
| })}> | ||
| Sign message | ||
| </button> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| Note: this message contains the Linea Mainnet chain ID (59144). | ||
|
|
||
| 4. Generate a link including this signed payload and redirect to it, or open it in an iframe. You | ||
| need to encode the JSON payload in base64 to safely include it in the Sumsub verification URL. | ||
|
|
||
| Payload to encode: | ||
|
|
||
| ```json | ||
| { | ||
| "signInMessage": "in.sumsub.com wants you to sign in with your Ethereum account:\n<WALLET_ADDRESS>\n\nI confirm that I am the owner of this wallet and consent to performing a risk assessment and issuing a Verax attestation to this address.\n\nURI: https://in.sumsub.com\nVersion: 1\nChain ID: 59144\nIssued At: <ISO_DATE>", | ||
| "signature": "<SIGNATURE>" | ||
| } | ||
| ``` | ||
|
|
||
| You then need to generate a base64-encoded JSON from this payload, to be used as the `msg` | ||
| parameter to append to the URL below: | ||
|
|
||
| ```typescript | ||
| const msg = btoa(JSON.stringify(payload)); | ||
| const url = new URL("https://in.sumsub.com/websdk/p/uni_BKWTkQpZ2EqnGoY7"); | ||
| url.search = new URLSearchParams({ msg }).toString(); | ||
| ``` | ||
|
|
||
| 5. The Sumsub verification process happens on their side. | ||
| 6. Once verified, the user is redirected back to your app, or your frontend receives a message event | ||
| from the iframe wrapper. Listening for the event: | ||
|
|
||
| ```typescript | ||
| const iframe = document.getElementById("sumsub-frame") as HTMLIFrameElement; | ||
| const ac = new AbortController(); | ||
|
|
||
| window.addEventListener( | ||
| "message", | ||
| (e: MessageEvent) => { | ||
| if (e.source !== iframe.contentWindow) return; | ||
| if (e.origin !== "https://in.sumsub.com") return; | ||
|
|
||
| if (e.data.status === "completed") { /* proceed */ } | ||
|
|
||
| ac.abort(); | ||
| }, | ||
| { signal: ac.signal }, | ||
| ); | ||
| ``` | ||
|
|
||
| Alternatively, you can redirect users back to your app via a callback URL instead of using an | ||
| iframe. | ||
|
|
||
| 7. You can then query the Linea PoH API to confirm that the attestation has been issued onchain. | ||
| Endpoint: `https://poh-api.linea.build/poh/v2/{address}` Documentation | ||
| [here](https://poh-api.linea.build/#/PoH/PohController_getOnePOHv2). | ||
|
|
||
| ### Notes | ||
|
|
||
| - The attestation is issued only if none exists for this wallet address. | ||
| - It may take a few seconds before the attestation is visible onchain after verification. | ||
| - Once verification is complete, you can programmatically confirm a user's PoH status using either | ||
| an API or an onchain contract (see below). | ||
|
|
||
| ## Check: how to confirm verification status | ||
|
|
||
| Once users have completed verification, you can confirm their Proof of Humanity status through an | ||
| API call or onchain verification. | ||
|
|
||
| The APIs presented below are free to use, without any key or authentication. | ||
|
|
||
| ### Offchain verification | ||
|
|
||
| The API base URL for the service is: `https://poh-api.linea.build/` | ||
|
|
||
| **Usage (GET):** | ||
|
|
||
| Call the endpoint with the format: `https://poh-api.linea.build/poh/v2/{address}` | ||
|
|
||
| **Example:** | ||
|
|
||
| ```bash | ||
| curl https://poh-api.linea.build/poh/v2/0xc5fd29cC1a1b76ba52873fF943FEDFDD36cF46C6 | ||
| # Content-Type: text/plain; | ||
| # Body: "true" | "false" | ||
| ``` | ||
|
|
||
| **Response:** | ||
|
|
||
| The response is raw text, not JSON. | ||
|
|
||
| - `false` = address does not have PoH status. | ||
| - `true` = address has PoH status. | ||
|
|
||
| **Example:** | ||
|
|
||
| ```typescript | ||
| const res = await fetch(`.../poh/v2/${address}`); | ||
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | ||
| const text = (await res.text()).trim(); // "true" | "false" | ||
| const isHuman = text === "true"; | ||
| ``` | ||
|
|
||
| **Reference:** | ||
|
|
||
| You can explore all available endpoints and test them directly from the [Swagger | ||
| UI](https://poh-api.linea.build/#/PoH/PohController_getOnePOH). | ||
|
|
||
| ### Signed onchain verification V2 | ||
|
|
||
| If you need fully onchain verification (e.g. from a smart contract), you can use the signed PoH | ||
| status, and the `PohVerifier` contract. The | ||
| [`PohVerifier.sol` contract](https://lineascan.build/address/0xBf14cFAFD7B83f6de881ae6dc10796ddD7220831) | ||
| can be used together with a trusted source of PoH status data. | ||
|
|
||
| #### 1. Get signed PoH status | ||
|
|
||
| The API base URL for the service is: `https://poh-signer-api.linea.build/` | ||
|
|
||
| **Usage (GET):** | ||
|
|
||
| Call the endpoint with the format: `https://poh-signer-api.linea.build/poh/v2/{address}` | ||
|
|
||
| **Example:** | ||
|
|
||
| ```bash | ||
| curl https://poh-signer-api.linea.build/poh/v2/0xc5fd29cC1a1b76ba52873fF943FEDFDD36cF46C6 | ||
| # Content-Type: text/plain; charset=utf-8 | ||
| # 0xa11a6c92fa0027d9de2a0c8ab363b1af083497da57f871c93aeb9efcd32ffaeb677fafb2c005e8165181713220b8a1da2f70ed31d7820b8fa086a8e7361dbf121c | ||
| ``` | ||
|
|
||
| This returns a signed message that contains the PoH status of the provided address. Response | ||
| format: plain text (not JSON). | ||
|
|
||
| Example response: | ||
|
|
||
| ``` | ||
| 0xa11a6c92fa0027d9de2a0c8ab363b1af083497da57f871c93aeb9efcd32ffaeb677fafb2c005e8165181713220b8a1da2f70ed31d7820b8fa086a8e7361dbf121c | ||
| ``` | ||
|
|
||
| Example: | ||
|
|
||
| ```typescript | ||
| const sig = await (await fetch(`https://poh-signer-api.linea.build/poh/v2/${address}`)).text(); | ||
| await pohVerifier.verify(sig, address); // See step 2 | ||
| ``` | ||
|
|
||
| #### 2. Call `PohVerifier.sol` | ||
|
|
||
| Call the `verify()` function with the signed message and the address of the account being queried. | ||
| The contract confirms that the signed message was issued by the trusted signer and returns a | ||
| boolean. | ||
|
|
||
| ```solidity | ||
| function verify( | ||
| bytes memory signature, | ||
| address human | ||
| ) external view virtual returns (bool){ ... } | ||
| ``` | ||
|
|
||
| Parameters: | ||
|
|
||
| - `signature`: The signed message from the previous step. | ||
| - `address`: The address of the account being queried. | ||
|
|
||
| It returns a boolean: | ||
|
|
||
| - `true`: The account has PoH status. | ||
| - `false`: The account does not have PoH status. | ||
|
|
||
| Example: | ||
|
|
||
| ```solidity | ||
| import { IPohVerifier } from "./interfaces/IPohVerifier.sol"; | ||
|
|
||
| error PohVerificationFailed(address sender); | ||
|
|
||
| /* ... */ | ||
|
|
||
| if (!pohVerifier.verify(signature, msg.sender)) { | ||
| revert PohVerificationFailed(msg.sender); | ||
| } | ||
|
|
||
| /* ... */ | ||
| ``` | ||
|
|
||
| ## Ensuring security and reliability | ||
|
|
||
| Proof of Humanity V2 is built with robust security, privacy, and transparency in mind. | ||
|
|
||
| - Trusted verification: All attestations are issued by Sumsub, a regulated KYC provider with | ||
| industry-standard compliance. | ||
| - Onchain transparency: Every verification is recorded through the Verax Attestation Registry, | ||
| ensuring public verifiability. | ||
| - User privacy: Only the attestation (not personal data) is stored onchain — maintaining a balance | ||
| between identity assurance and privacy. | ||
| - System reliability: The PoH API handles rate limits, temporary downtimes, and blockchain | ||
| propagation gracefully, ensuring developers always receive accurate verification states. | ||
|
|
||
| Developers can confidently integrate PoH knowing it provides a secure, composable, and future-proof | ||
| foundation for identity-driven experiences. | ||
|
|
||
| ### A note on privacy | ||
|
|
||
| When users complete the verification flow, Sumsub processes a face-video and biometric images to | ||
| check for liveness and identity-document match, but they only stores a hash, they do not store the | ||
| full image. | ||
|
|
||
| Sumsub adheres to the General Data Protection Regulation (GDPR) principles (lawful, transparent, | ||
| purpose-limited, minimal, secure), and implements ISO/IEC 27001/27017/27018, SOC 2 Type 2 and | ||
| PCI-DSS security frameworks, giving users the best standards for data protection and privacy. | ||
|
|
||
| For our most privacy savvy users, we're working on a privacy-preserving verification alternative that | ||
| will give you further control while still ensuring "one-human-one-wallet" trust. | ||
|
|
||
| ## Conclusion | ||
|
|
||
| Proof of Humanity V2 is a true enable of growth for all builders and dapps on the Linea ecosystem. | ||
| By giving dapps the ability to verify real humans, easily, securely, and onchain, it helps transform | ||
| the way web3 applications grow and interact with their communities. | ||
|
|
||
| If you're building on Linea, PoH V2 is now live and available through our developer documentation. | ||
|
|
||
| Start verifying your users today, and build experiences meant for real humans. | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Bug
The Sumsub iframe integration example has two issues: the
AbortControlleron line 175 removes the event listener prematurely, which can cause the app to miss the "completed" status. Also, the tutorial references an iframe by ID ('sumsub-frame') without explaining how to create it.