Self Protocol Integration
Self lets users prove identity attributes (age, nationality, humanity) from passports/ID cards using zero-knowledge proofs — no personal data exposed. Users scan their document's NFC chip in the Self mobile app and share a zk proof with your app.
Quick Start (Next.js Off-Chain)
1. Install
npm install @selfxyz/qrcode @selfxyz/core
2. Frontend — QR Code Component
"use client";
import { SelfQRcodeWrapper, SelfAppBuilder } from "@selfxyz/qrcode";
export default function VerifyIdentity({ userId }: { userId: string }) {
const selfApp = new SelfAppBuilder({
appName: "My App",
scope: "my-app-scope",
endpoint: "https://yourapp.com/api/verify",
endpointType: "https",
userId,
userIdType: "hex",
disclosures: {
minimumAge: 18,
},
}).build();
return (
<SelfQRcodeWrapper
selfApp={selfApp}
=> console.log("Verified")}
type="websocket"
darkMode={false}
/>
);
}
3. Backend — Verification Endpoint
// app/api/verify/route.ts
import { SelfBackendVerifier, DefaultConfigStore } from "@selfxyz/core";
export async function POST(req: Request) {
const { proof, publicSignals } = await req.json();
const verifier = new SelfBackendVerifier(
"my-app-scope", // must match frontend scope
"https://yourapp.com/api/verify", // must match frontend endpoint
true, // true = accept mock passports (dev only)
null, // allowedIds (null = all)
new DefaultConfigStore({ // must match frontend disclosures
minimumAge: 18,
})
);
const result = await verifier.verify(proof, publicSignals);
return Response.json({
verified: result.isValid,
nationality: result.credentialSubject?.nationality,
});
}
Integration Patterns
| Pattern |
When to Use |
endpoint |
endpointType |
| Off-chain (backend) |
Web apps, APIs, most cases |
Your API URL |
"https" or "https-staging" |
| On-chain (contract) |
DeFi, token gating, airdrops |
Contract address (lowercase) |
"celo" or "celo-staging" |
| Deep linking |
Mobile-first flows |
Your API URL |
"https" |
- Off-chain: Fastest to implement. Proof sent to your backend, verified server-side.
- On-chain: Proof verified by Celo smart contract. Inherit
SelfVerificationRoot. Use for trustless/permissionless scenarios.
- Deep linking: For mobile users — opens Self app directly instead of QR scan. See
references/frontend.md.
Critical Gotchas
Config matching is mandatory — Frontend disclosures must EXACTLY match backend/contract verification config. Mismatched age thresholds, country lists, or OFAC settings cause silent failures.
Contract addresses must be lowercase — Non-checksum format in frontend endpoint. Use .toLowerCase().
Country codes are ISO 3-letter — e.g., "USA", "IRN", "PRK". Max 40 countries in exclusion lists.
Mock passports = testnet only — Set mockPassport: true in backend / use "celo-staging" endpoint type. Real passports require mainnet. To create a mock passport: open Self app, tap the Passport button 5 times. Mock testing requires OFAC disabled.
Version requirement — @selfxyz/core >= 1.1.0-beta.1.
Attestation IDs — 1 = Passport, 2 = Biometric ID Card. Must explicitly allow via allowedIds map.
Scope uniqueness — On-chain, scope is Poseidon-hashed with contract address, preventing cross-contract proof replay.
Endpoint must be publicly accessible — Self app sends proof directly to your endpoint. Use ngrok for local development.
Common errors: ScopeMismatch = scope/address mismatch or non-lowercase address. Invalid 'to' Address = wrong endpointType (celo vs https). InvalidIdentityCommitmentRoot = real passport on testnet (use mainnet). Invalid Config ID = mock passport on mainnet (use testnet).
Deployed Contracts (Celo)
| Network |
Address |
| Mainnet Hub V2 |
0xe57F4773bd9c9d8b6Cd70431117d353298B9f5BF |
| Sepolia Hub V2 |
0x16ECBA51e18a4a7e61fdC417f0d47AFEeDfbed74 |
| Sepolia Staging Hub V2 |
0x68c931C9a534D37aa78094877F46fE46a49F1A51 |
References
Load these for deeper integration details:
references/frontend.md — SelfAppBuilder full config, SelfQRcodeWrapper props, deep linking with getUniversalLink, disclosure options
references/backend.md — SelfBackendVerifier constructor details, DefaultConfigStore vs InMemoryConfigStore, verification result schema, dynamic configs
references/contracts.md — SelfVerificationRoot inheritance pattern, Hub V2 interaction, setVerificationConfigV2, customVerificationHook, getConfigId, userDefinedData patterns
1---2name: self-xyz3description: Integrate Self (self.xyz) — a privacy-first identity protocol using zero-knowledge proofs to verify passports and ID cards. Use when the user mentions Self protocol, Self identity, self.xyz, passport verification, zero-knowledge identity verification, SelfAppBuilder, SelfBackendVerifier, SelfVerificationRoot, or wants to add privacy-preserving KYC, age verification, nationality checks, OFAC screening, or Sybil resistance using real-world identity documents. Covers frontend QR code integration, backend proof verification, and on-chain smart contract verification on Celo.4---5
6# Self Protocol Integration
7
8Self lets users prove identity attributes (age, nationality, humanity) from passports/ID cards using zero-knowledge proofs — no personal data exposed. Users scan their document's NFC chip in the Self mobile app and share a zk proof with your app.
9
10## Quick Start (Next.js Off-Chain)
11
12### 1. Install
13
14```bash
15npm install @selfxyz/qrcode @selfxyz/core
16```
17
18### 2. Frontend — QR Code Component
19
20```tsx
21"use client";
22import { SelfQRcodeWrapper, SelfAppBuilder } from "@selfxyz/qrcode";
23
24export default function VerifyIdentity({ userId }: { userId: string }) {
25 const selfApp = new SelfAppBuilder({
26 appName: "My App",
27 scope: "my-app-scope",
28 endpoint: "https://yourapp.com/api/verify",
29 endpointType: "https",
30 userId,
31 userIdType: "hex",
32 disclosures: {
33 minimumAge: 18,
34 },
35 }).build();
36
37 return (
38 <SelfQRcodeWrapper
39 selfApp={selfApp}
40 onSuccess={() => console.log("Verified")}
41 type="websocket"
42 darkMode={false}
43 />
44 );
45}
46```
47
48### 3. Backend — Verification Endpoint
49
50```ts
51// app/api/verify/route.ts
52import { SelfBackendVerifier, DefaultConfigStore } from "@selfxyz/core";
53
54export async function POST(req: Request) {
55 const { proof, publicSignals } = await req.json();
56
57 const verifier = new SelfBackendVerifier(
58 "my-app-scope", // must match frontend scope
59 "https://yourapp.com/api/verify", // must match frontend endpoint
60 true, // true = accept mock passports (dev only)
61 null, // allowedIds (null = all)
62 new DefaultConfigStore({ // must match frontend disclosures
63 minimumAge: 18,
64 })
65 );
66
67 const result = await verifier.verify(proof, publicSignals);
68
69 return Response.json({
70 verified: result.isValid,
71 nationality: result.credentialSubject?.nationality,
72 });
73}
74```
75
76## Integration Patterns
77
78| Pattern | When to Use | `endpoint` | `endpointType` |
79|---------|------------|------------|----------------|
80| **Off-chain** (backend) | Web apps, APIs, most cases | Your API URL | `"https"` or `"https-staging"` |
81| **On-chain** (contract) | DeFi, token gating, airdrops | Contract address (lowercase) | `"celo"` or `"celo-staging"` |
82| **Deep linking** | Mobile-first flows | Your API URL | `"https"` |
83
84- **Off-chain**: Fastest to implement. Proof sent to your backend, verified server-side.
85- **On-chain**: Proof verified by Celo smart contract. Inherit `SelfVerificationRoot`. Use for trustless/permissionless scenarios.
86- **Deep linking**: For mobile users — opens Self app directly instead of QR scan. See `references/frontend.md`.
87
88## Critical Gotchas
89
901. **Config matching is mandatory** — Frontend `disclosures` must EXACTLY match backend/contract verification config. Mismatched age thresholds, country lists, or OFAC settings cause silent failures.
91
922. **Contract addresses must be lowercase** — Non-checksum format in frontend `endpoint`. Use `.toLowerCase()`.
93
943. **Country codes are ISO 3-letter** — e.g., `"USA"`, `"IRN"`, `"PRK"`. Max 40 countries in exclusion lists.
95
964. **Mock passports = testnet only** — Set `mockPassport: true` in backend / use `"celo-staging"` endpoint type. Real passports require mainnet. To create a mock passport: open Self app, tap the Passport button **5 times**. Mock testing requires OFAC disabled.
97
985. **Version requirement** — `@selfxyz/core` >= 1.1.0-beta.1.
99
1006. **Attestation IDs** — `1` = Passport, `2` = Biometric ID Card. Must explicitly allow via `allowedIds` map.
101
1027. **Scope uniqueness** — On-chain, scope is Poseidon-hashed with contract address, preventing cross-contract proof replay.
103
1048. **Endpoint must be publicly accessible** — Self app sends proof directly to your endpoint. Use ngrok for local development.
105
1069. **Common errors**: `ScopeMismatch` = scope/address mismatch or non-lowercase address. `Invalid 'to' Address` = wrong `endpointType` (celo vs https). `InvalidIdentityCommitmentRoot` = real passport on testnet (use mainnet). `Invalid Config ID` = mock passport on mainnet (use testnet).
107
108## Deployed Contracts (Celo)
109
110| Network | Address |
111|---------|---------|
112| **Mainnet** Hub V2 | `0xe57F4773bd9c9d8b6Cd70431117d353298B9f5BF` |
113| **Sepolia** Hub V2 | `0x16ECBA51e18a4a7e61fdC417f0d47AFEeDfbed74` |
114| **Sepolia Staging** Hub V2 | `0x68c931C9a534D37aa78094877F46fE46a49F1A51` |
115
116## References
117
118Load these for deeper integration details:
119
120- **`references/frontend.md`** — `SelfAppBuilder` full config, `SelfQRcodeWrapper` props, deep linking with `getUniversalLink`, disclosure options
121- **`references/backend.md`** — `SelfBackendVerifier` constructor details, `DefaultConfigStore` vs `InMemoryConfigStore`, verification result schema, dynamic configs
122- **`references/contracts.md`** — `SelfVerificationRoot` inheritance pattern, Hub V2 interaction, `setVerificationConfigV2`, `customVerificationHook`, `getConfigId`, `userDefinedData` patterns