├── .eslintrc.json ├── src ├── app │ ├── globals.css │ ├── favicon.ico │ ├── page.tsx │ ├── layout.tsx │ ├── components │ │ ├── SignMessages.tsx │ │ ├── SendUserTransaction.tsx │ │ └── PrivyDemo.tsx │ └── airdrop │ │ └── page.tsx └── Providers │ └── PrivyProvider.tsx ├── next.config.mjs ├── postcss.config.js ├── .env.example ├── .gitignore ├── tailwind.config.ts ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blixor7/Zemptin-wallet/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PRIVY_APP_ID = 2 | NEXT_PUBLIC_PRIVY_APP_SECRET = 3 | 4 | 5 | JWKS = 6 | Verify_with_key = 7 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import PrivyDemo from "./components/PrivyDemo"; 2 | export default function Home() { 3 | return ( 4 |
5 | 6 |
7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | .env -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | import Providers from "@/Providers/PrivyProvider"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export const metadata: Metadata = { 9 | title: "Metaline-x Airdrop", 10 | description: "Airdrop for Metaline-x community", 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: Readonly<{ 16 | children: React.ReactNode; 17 | }>) { 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "privy_demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@privy-io/react-auth": "^1.60.1", 13 | "ethers": "5.7.2", 14 | "next": "14.1.4", 15 | "react": "^18", 16 | "react-dom": "^18" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^20", 20 | "@types/react": "^18", 21 | "@types/react-dom": "^18", 22 | "autoprefixer": "^10.0.1", 23 | "eslint": "^8", 24 | "eslint-config-next": "14.1.4", 25 | "postcss": "^8", 26 | "tailwindcss": "^3.3.0", 27 | "typescript": "^5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Providers/PrivyProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { PrivyProvider } from "@privy-io/react-auth"; 4 | 5 | const handleLogin = () => { 6 | console.log("User login Successfully"); 7 | }; 8 | 9 | function Providers({ children }: { children: React.ReactNode }) { 10 | return ( 11 | 33 | {children} 34 | 35 | ); 36 | } 37 | 38 | export default Providers; 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zemptin Wallet Integration 2 | 3 | This repository demonstrates how to integrate **Zemptin Wallet** for user authentication in a web application. Privy offers a secure, simple, and user-friendly wallet experience, making it ideal for decentralized applications (dApps) seeking a streamlined onboarding and authentication process. 4 | 5 | ## Introduction 6 | The goal of this example is to provide a quick and easy way to integrate Zemptin Wallet into your project. Zemptin Wallet allows users to seamlessly authenticate and interact with your decentralized app while keeping their data secure. 7 | 8 | ## Features 9 | - **User Authentication**: Simple and secure user login with Zemptin Wallet. 10 | - **Wallet Management**: Streamlined wallet experience tailored for decentralized applications. 11 | - **User-Friendly Interface**: Designed with ease of use in mind to reduce friction in user onboarding. 12 | 13 | ## Setup and Installation 14 | 15 | 1. **Clone the repository** 16 | ```bash 17 | git clone https://github.com/blixor7/Zemptin-wallet.git 18 | 19 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/SignMessages.tsx: -------------------------------------------------------------------------------- 1 | import { describe } from "node:test"; 2 | import React, { useState } from "react"; 3 | 4 | const SignMessages = ({ 5 | user, 6 | signMessage, 7 | }: { 8 | user: any; 9 | signMessage: any; 10 | }) => { 11 | const [hasSigned, setHasSigned] = useState(false); 12 | const [signature, setSignature] = useState(""); 13 | 14 | const message = "This is test message for the sign message"; 15 | const uiConfig = { 16 | title: "Testing Signature Feature", 17 | description: "This is a demo test the signing feature", 18 | buttonText: "Sign the message", 19 | }; 20 | 21 | const signUserMessage = async () => { 22 | const signature = await signMessage(message, uiConfig); 23 | setHasSigned(true); 24 | setSignature(signature); 25 | }; 26 | 27 | return ( 28 |
29 | 36 | {hasSigned && ( 37 |
38 |

Signed message with signature

39 |

{signature}

40 |
41 | )} 42 |
43 | ); 44 | }; 45 | 46 | export default SignMessages; 47 | -------------------------------------------------------------------------------- /src/app/components/SendUserTransaction.tsx: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { describe } from "node:test"; 3 | import React from "react"; 4 | 5 | const SendUserTransaction = ({ 6 | user, 7 | sendTransaction, 8 | }: { 9 | user: any; 10 | sendTransaction: any; 11 | }) => { 12 | const sendTx = async () => { 13 | const ethAmount = ethers.utils.parseEther("0.01"); 14 | const hexAmount = ethers.utils.hexlify(ethAmount); 15 | 16 | const unsingedTx = { 17 | to: "0x9E51492A995e9275C21d79923Ed510B8B31B66C5", 18 | chainId: "17000", 19 | value: hexAmount, 20 | }; 21 | 22 | const txUiConfig = { 23 | header: "Send Transaction", 24 | describe: "send 0.001 ETH", 25 | buttonText: "Send", 26 | }; 27 | 28 | if (user.wallet) { 29 | const tx = await sendTransaction(unsingedTx, txUiConfig); 30 | } 31 | }; 32 | return ( 33 |
34 | 41 | {/* {hasSigned && ( 42 |
43 |

Signed message with signature

44 |

{signature}

45 |
46 | )} */} 47 |
48 | ); 49 | }; 50 | 51 | export default SendUserTransaction; 52 | -------------------------------------------------------------------------------- /src/app/components/PrivyDemo.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect } from "react"; 3 | import { usePrivy, useLogin, useWallets } from "@privy-io/react-auth"; 4 | import { useRouter } from "next/navigation"; 5 | const PrivyDemo = () => { 6 | const router = useRouter(); 7 | const { ready, authenticated } = usePrivy(); 8 | const { wallets } = useWallets(); 9 | 10 | console.log(" ready, authenticated", ready, authenticated); 11 | 12 | const { login } = useLogin({ 13 | onComplete: async (user, isNewUser, wasAlreadyAuthenticated) => { 14 | console.log("user", user, wasAlreadyAuthenticated); 15 | console.log("isNewUser", isNewUser); 16 | console.log("wasAlreadyAuthenticated", wasAlreadyAuthenticated); 17 | // Any logic you'd like to execute if the user is/becomes authenticated while this 18 | // component is mounted 19 | 20 | const wallet = wallets[0]; 21 | const chainId = wallet?.chainId; 22 | if (chainId !== "17000") { 23 | await wallet?.switchChain(17000); 24 | } 25 | }, 26 | onError: (error) => { 27 | console.log(error); 28 | // Any logic you'd like to execute after a user exits the login flow or there is an error 29 | }, 30 | }); 31 | 32 | useEffect(() => { 33 | if (ready && authenticated) router.replace("/airdrop"); 34 | }, [ready, authenticated]); 35 | 36 | if (!ready) return <>; 37 | return ( 38 |
39 |

Privy Wallet Demo

40 | 41 |
42 | 48 |
49 |
50 | ); 51 | }; 52 | 53 | export default PrivyDemo; 54 | -------------------------------------------------------------------------------- /src/app/airdrop/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect, useState } from "react"; 3 | import { usePrivy, useWallets, useLinkAccount } from "@privy-io/react-auth"; 4 | import { useLogout } from "@privy-io/react-auth"; 5 | import { useRouter } from "next/navigation"; 6 | import { ethers } from "ethers"; 7 | 8 | import SignMessages from "../components/SignMessages"; 9 | import SendUserTransaction from "../components/SendUserTransaction"; 10 | 11 | const Airdrop = () => { 12 | const router = useRouter(); 13 | const [selectedLink, setSelectedLink] = useState(""); 14 | const [walletBalance, setWalletBalance] = useState(""); 15 | const [embeddedWallet, setEmbeddedWallet] = useState(null); 16 | 17 | const { authenticated, user, ready, sendTransaction, signMessage } = 18 | usePrivy(); 19 | 20 | const { 21 | linkGoogle, 22 | linkEmail, 23 | linkWallet, 24 | linkGithub, 25 | linkDiscord, 26 | linkTwitter, 27 | } = useLinkAccount({ 28 | onSuccess: (user, linkedAccountType) => { 29 | console.log("Successfully linked", user, linkedAccountType); 30 | // Any logic you'd like to execute if the user successfully links an account while this 31 | // component is mounted 32 | }, 33 | onError: (error) => { 34 | console.log(error); 35 | alert(error); 36 | // Any logic you'd like to execute after a user exits the link flow or there is an error 37 | }, 38 | }); 39 | 40 | const linkOptions = [ 41 | { label: "Email", action: linkEmail }, 42 | { label: "Wallet", action: linkWallet }, 43 | { label: "Google", action: linkGoogle }, 44 | { label: "Github", action: linkGithub }, 45 | { label: "Discord", action: linkDiscord }, 46 | { label: "Twitter", action: linkTwitter }, 47 | ]; 48 | 49 | const handleLink = async () => { 50 | const selected: any = linkOptions.find( 51 | (options: any) => options?.label === selectedLink 52 | ); 53 | 54 | if (selected) { 55 | selected.action(); 56 | } 57 | }; 58 | 59 | const { logout } = useLogout({ 60 | onSuccess: () => { 61 | console.log("User logged out"); 62 | }, 63 | }); 64 | 65 | const { wallets } = useWallets(); 66 | console.log("Wallet Info", wallets); 67 | 68 | useEffect(() => { 69 | if (!ready) { 70 | return; 71 | } else { 72 | setUp(); 73 | } 74 | 75 | async function setUp() { 76 | const embeddedWallet = wallets.find( 77 | (wallet) => wallet.walletClientType === "privy" 78 | ); 79 | 80 | if (embeddedWallet) { 81 | const provider = await embeddedWallet.getEthereumProvider(); 82 | await provider.request({ 83 | method: "wallet_switchEthereumChain", 84 | params: [{ chainId: `0x${Number(17000).toString(16)}` }], 85 | }); 86 | 87 | const ethProviders = new ethers.providers.Web3Provider(provider); 88 | 89 | const walletBalance = await ethProviders.getBalance( 90 | embeddedWallet.address 91 | ); 92 | const ethToString = ethers.utils.formatEther(walletBalance); 93 | setWalletBalance(ethToString); 94 | setEmbeddedWallet(embeddedWallet.address); 95 | } 96 | } 97 | }, [wallets, ready]); 98 | 99 | useEffect(() => { 100 | if (!user || !authenticated) router.replace("/"); 101 | }, [user, authenticated]); 102 | 103 | if (!user) return <>No User; 104 | return ( 105 |
106 |

Airdrop

107 | 108 |
109 | 115 |
116 |

User {user?.id} has linked the following accounts:

117 |
    118 |
  • Email: {user?.email ? user?.email.address : "None"}
  • 119 |
  • Wallet: {user?.wallet ? user?.wallet.address : "None"}
  • 120 |
  • Google: {user?.google ? user?.google.email : "None"}
  • 121 |
  • Apple: {user?.apple ? user?.apple.email : "None"}
  • 122 |
  • Discord: {user?.discord ? user?.discord.username : "None"}
  • 123 |
  • Twitter: {user?.twitter ? user?.twitter.username : "None"}
  • 124 |
  • GitHub: {user?.github ? user?.github.username : "None"}
  • 125 |
  • TikTok: {user?.tiktok ? user?.tiktok?.name : "None"}
  • 126 |
  • LinkedIn: {user?.linkedin ? user?.linkedin.email : "None"}
  • 127 |
  • 128 | Farcaster: {user?.farcaster ? user?.farcaster.username : "None"} 129 |
  • 130 |
  • Spotify: {user?.spotify ? user?.spotify.email : "None"}
  • 131 |
132 |
133 | 134 | {/* Select Link */} 135 | 136 |
137 | 150 | 151 | 157 |
158 |
159 |
The Wallet Address :{embeddedWallet}
160 |
Wallet Balance :{walletBalance}
161 |
162 | 163 |
164 | 165 |
166 | 167 |
168 | 169 |
170 |
171 |
172 | ); 173 | }; 174 | 175 | export default Airdrop; 176 | --------------------------------------------------------------------------------