mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-06 23:03:07 +00:00
add ZKPassport age verification (livestream code)
This commit is contained in:
parent
c91164dbde
commit
fefe7608ad
2121
package-lock.json
generated
2121
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -50,8 +50,11 @@
|
|||||||
"@reown/appkit-adapter-wagmi": "^1.7.17",
|
"@reown/appkit-adapter-wagmi": "^1.7.17",
|
||||||
"@reown/appkit-wallet-button": "^1.7.17",
|
"@reown/appkit-wallet-button": "^1.7.17",
|
||||||
"@tanstack/react-query": "^5.84.1",
|
"@tanstack/react-query": "^5.84.1",
|
||||||
|
"@types/qrcode.react": "^1.0.5",
|
||||||
"@waku/sdk": "^0.0.35-67a7287.0",
|
"@waku/sdk": "^0.0.35-67a7287.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"@zkpassport/sdk": "^0.8.3",
|
||||||
|
"bitcoinjs-message": "^2.2.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
@ -62,6 +65,7 @@
|
|||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"ordiscan": "^1.3.0",
|
"ordiscan": "^1.3.0",
|
||||||
"re-resizable": "6.11.2",
|
"re-resizable": "6.11.2",
|
||||||
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
|||||||
83
src/lib/zkPassport.ts
Normal file
83
src/lib/zkPassport.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { EU_COUNTRIES, ZKPassport } from '@zkpassport/sdk';
|
||||||
|
|
||||||
|
export const verifyAge = async (setProgress: (status:string) => void, setUrl: (url:string) => void): Promise<boolean> => {
|
||||||
|
const zkPassport = new ZKPassport();
|
||||||
|
|
||||||
|
const queryBuilder = await zkPassport.request({
|
||||||
|
name: "OpChan",
|
||||||
|
logo: "https://zkpassport.id/logo.png",
|
||||||
|
purpose: "Prove you are 18+ years old",
|
||||||
|
scope: "adult",
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
url,
|
||||||
|
onResult,
|
||||||
|
onGeneratingProof,
|
||||||
|
onError,
|
||||||
|
onProofGenerated,
|
||||||
|
onReject,
|
||||||
|
onRequestReceived
|
||||||
|
} = queryBuilder.gte("age", 18).done();
|
||||||
|
|
||||||
|
setUrl(url);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
console.log("Starting age verification with zkPassport");
|
||||||
|
onRequestReceived(() => {
|
||||||
|
setProgress("Request received, preparing for age verification");
|
||||||
|
console.log("Request received, preparing for age verification");
|
||||||
|
});
|
||||||
|
|
||||||
|
onGeneratingProof(() => {
|
||||||
|
setProgress("Generating cryptographic proof of age");
|
||||||
|
console.log("Generating cryptographic proof of age");
|
||||||
|
});
|
||||||
|
|
||||||
|
onProofGenerated(() => {
|
||||||
|
setProgress("Age proof generated successfully");
|
||||||
|
console.log("Age proof generated successfully");
|
||||||
|
});
|
||||||
|
|
||||||
|
onReject(() => {
|
||||||
|
setProgress("Age verification request was rejected");
|
||||||
|
console.log("Age verification request was rejected by the user");
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
onError((error) => {
|
||||||
|
setProgress(`Age verification error: ${error}`);
|
||||||
|
console.error("Age verification error", error);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
onResult(({ verified, uniqueIdentifier, result }) => {
|
||||||
|
console.log("Age verification callback", verified, uniqueIdentifier, result);
|
||||||
|
try {
|
||||||
|
console.log("Age verification result", verified, result);
|
||||||
|
if (verified) {
|
||||||
|
const isOver18 = result.age?.gte?.result;
|
||||||
|
setProgress("Age verification completed successfully");
|
||||||
|
resolve(isOver18 || false);
|
||||||
|
console.log("User is 18+ years old", isOver18);
|
||||||
|
} else {
|
||||||
|
setProgress("Age verification failed");
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Age verification result processing error", error);
|
||||||
|
setProgress(`Age verification result processing error: ${error}`);
|
||||||
|
resolve(false);
|
||||||
|
} finally {
|
||||||
|
setUrl('');
|
||||||
|
setProgress('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Age verification exception", error);
|
||||||
|
setProgress(`Age verification exception: ${error}`);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@ import { DelegationFullStatus } from '@/lib/delegation';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { verifyAge } from '@/lib/zkPassport';
|
||||||
|
import { QRCodeCanvas } from 'qrcode.react';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -63,6 +65,9 @@ export default function ProfilePage() {
|
|||||||
EDisplayPreference.WALLET_ADDRESS
|
EDisplayPreference.WALLET_ADDRESS
|
||||||
);
|
);
|
||||||
const [walletWizardOpen, setWalletWizardOpen] = useState(false);
|
const [walletWizardOpen, setWalletWizardOpen] = useState(false);
|
||||||
|
const [url, setUrl] = useState<string>('');
|
||||||
|
const [progress, setProgress] = useState<string>('');
|
||||||
|
const [isVerifying, setIsVerifying] = useState<boolean>(false);
|
||||||
|
|
||||||
// Initialize and update local state when user data changes
|
// Initialize and update local state when user data changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -587,6 +592,61 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{/* Age Verification Section */}
|
||||||
|
<div className="max-w-md mx-auto mt-8 p-6 bg-cyber-muted/20 border border-cyber-muted/30 rounded-lg">
|
||||||
|
<h2 className="text-xl font-bold text-white mb-4">Age Verification</h2>
|
||||||
|
<p className="text-cyber-neutral mb-4">
|
||||||
|
Verify your age to access restricted content.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
setIsVerifying(true);
|
||||||
|
try {
|
||||||
|
const result = await verifyAge(setProgress, setUrl);
|
||||||
|
console.log('Age verification result:', result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Age verification failed:', error);
|
||||||
|
} finally {
|
||||||
|
setIsVerifying(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isVerifying}
|
||||||
|
className="w-full bg-cyber-accent hover:bg-cyber-accent/80 text-black font-mono"
|
||||||
|
>
|
||||||
|
{isVerifying ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Verifying...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Verify Age'
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{progress && (
|
||||||
|
<p className="mt-4 text-sm text-cyber-neutral">{progress}</p>
|
||||||
|
)}
|
||||||
|
{url && (
|
||||||
|
<div className="mt-4 space-y-4">
|
||||||
|
<div className="text-center">
|
||||||
|
<a
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-cyber-accent hover:underline font-medium"
|
||||||
|
>
|
||||||
|
Open verification in new tab
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center p-4 bg-white rounded-lg shadow-lg inline-block">
|
||||||
|
<QRCodeCanvas value={url} size={200} level="H" includeMargin={true} />
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-cyber-neutral text-center">
|
||||||
|
Scan this QR code to open the verification page on your mobile device
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer className="page-footer">
|
<footer className="page-footer">
|
||||||
<p>OpChan - A decentralized forum built on Waku & Bitcoin Ordinals</p>
|
<p>OpChan - A decentralized forum built on Waku & Bitcoin Ordinals</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user