Privy
Enable users to login with their social accounts (Google, GitHub, etc.)
Privy is a third-party service that allows users to login using their email or social accounts (Google, Facebook, Twitter, etc). Upon login, a NEAR wallet is created for the user, which they can fund and use to interact with your dApp.

[!TIP]
See full working example here
[!NOTE]
Web3Auth For an alternative social login method you can check Web3Auth. Our Web3Auth Integration Example includes a Modal integration that derives a NEAR Ed25519 key from the authenticated session and an MPC Core Kit integration that signs through a custom SDK
Signer. Both usenear-api-jsto query NEAR and submit transactions.
Installation
Choose whether to expose your Privy wallet through NEAR Connect or integrate Privy directly into your application:
[tab] NEAR Connect
Install the plugin, NEAR Connect, and the UI packages used by the signing page:
npm install @peerfolio/privy-near-connect @hot-labs/near-connect @radix-ui/themes @radix-ui/react-collapsible @radix-ui/react-icons
[tab] Direct Privy
npm install @privy-io/react-auth near-api-js @noble/hashes
Which should I choose?
Using NEAR Connect would give your application access to all NEAR Wallets that support NEAR Connect, plus social login with Privy. This will provide an experience closer to Web3 applications.
Integrating Privy directly would make your application support only social login (through Privy). This should be the preferred way for applications that want to abstract away the wallet and provide a Web2-like experience to their users.
Setup
[tab] NEAR Connect
privy-near-connectlets you expose a Privy embedded wallet through NEAR Connect.1. Create a Privy app
Create an application in the Privy dashboard and copy its App ID and Client ID. Store both values in your application's environment variables:
VITE_PRIVY_APP_ID=your-privy-app-id VITE_PRIVY_APP_CLIENT_ID=your-privy-client-id2. Publish a wallet manifest
Add
manifest.jsonto your application's public directory. Replace the example URLs and wallet metadata with your own:{ "version": "1.0.0", "wallets": [ { "id": "myprivywallet", "name": "My Privy Wallet", "icon": "https://yourdapp.com/favicon.ico", "description": "Web wallet for NEAR", "website": "https://yourdapp.com", "version": "1.0.0", "executor": "https://raw.githubusercontent.com/beneviolabs/privy-near-connect/3d56b782669d242a1ea432921e291abc26b4ebc7/executor.js", "type": "sandbox", "platform": { "web": "https://yourdapp.com" }, "features": { "signMessage": true, "signInWithoutAddKey": true, "signInAndSignMessage": true, "signInWithFunctionCallKey": true, "signAndSendTransaction": true, "signAndSendTransactions": true, "mainnet": true, "testnet": true }, "permissions": { "isPrivyConnect": true, "storage": true, "allowsOpen": [ "https://yourdapp.com" ] }, "metadata": { "signPageURL": "https://yourdapp.com/sign" } } ] }[!WARNING]
Keep the
executorURL pinned to an immutable commit SHA. NEAR Connect fetches and runs this script at runtime, so you should update the SHA deliberately when adopting a new executor build.3. Create the signing page
Add a
/signroute and render the plugin's signing component there:import Privy, { LocalStorage } from '@privy-io/js-sdk-core' import { SignPagePlugin } from '@peerfolio/privy-near-connect/sign-page-plugin' import '@radix-ui/themes/styles.css' import '@peerfolio/privy-near-connect/sign-page-plugin/theme.css' const privy = new Privy({ appId: import.meta.env.VITE_PRIVY_APP_ID, clientId: import.meta.env.VITE_PRIVY_APP_CLIENT_ID, storage: new LocalStorage(), }) export default function SignPage() { return ( <SignPagePlugin privy={privy} options={{ allowedOrigins: ['https://yourdapp.com'] }} /> ) }
allowedOriginslimits which applications can send requests to the signing page. Add every dApp origin that should be able to use the wallet.4. Load the plugin with NEAR Connect
Initialize
NearConnectorwith the manifest you created:import { NearConnector } from '@hot-labs/near-connect' const connector = new NearConnector({ manifest: '/manifest.json', network: 'testnet', })That's it! The plugin will now be available in the NEAR Connect modal.
[!TIP]
Continue with the wallet login tutorial to connect the wallet and use it from your dApp.
[tab] Direct Privy
Use Node.js 22.22.2 or newer. Wrap your application with
PrivyProviderand a customNEARxPrivycontext that bridges Privy's authentication with a NEAR account:import { Navigation } from './components/navigation' import Home from './pages/home' import HelloNear from './pages/hello_near' import { BrowserRouter, HashRouter, Routes, Route } from 'react-router' import { PrivyProvider } from '@privy-io/react-auth' import { NEARxPrivy } from './context/provider' function App() { // Use HashRouter when deployed under a subpath (GitHub Pages) const useHashRouter = import.meta.env.BASE_URL !== '/' const Router = useHashRouter ? HashRouter : BrowserRouter return ( <PrivyProvider appId="cme1neps40007jy0b5hyesl0s" config={{ appearance: { accentColor: '#6A6FF5', theme: '#FFFFFF', showWalletLoginFirst: false, logo: 'https://auth.privy.io/logos/privy-logo.png', }, loginMethods: ['email', 'google', 'github'], }} > <NEARxPrivy> <Router> <Navigation /> <Routes> <Route path="/" element={<Home />} /> <Route path="/hello-near" element={<HelloNear />} /> </Routes> </Router> </NEARxPrivy> </PrivyProvider> ) } export default App[accordion] Get a Privy App ID
- Sign up at privy.io and create a new app.
- Copy the App ID from the dashboard and set it as your
appIdprop.- Configure your allowed login methods and other settings in the Privy dashboard.
Context
The
NEARxPrivyprovider listens to Privy's authentication state and selects the NEAR wallet from the user's linked accounts. If the user has no NEAR wallet, it creates one. It then exposes anearAccountinstance through context:// Privy import { usePrivy } from '@privy-io/react-auth' import { useCreateWallet } from '@privy-io/react-auth/extended-chains' import { useSignRawHash } from '@privy-io/react-auth/extended-chains' // near api js import { JsonRpcProvider } from 'near-api-js' import { Account } from 'near-api-js' // config import { useEffect, useRef, type PropsWithChildren } from 'react' import { useState } from 'react' import { NearContext } from './useNear' import { providerUrl } from '../config' import { privySigner } from './signer' // Provider const provider = new JsonRpcProvider({ url: providerUrl }) export function NEARxPrivy({ children }: PropsWithChildren) { const { authenticated, user } = usePrivy() const [walletId, setWalletId] = useState('') const [nearAccount, setNearAccount] = useState<Account | null>(null) const { signRawHash } = useSignRawHash() const { createWallet } = useCreateWallet() const creatingWallet = useRef(false) useEffect(() => { if (!authenticated || !user) { setWalletId('') setNearAccount(null) return } // A user may also have an Ethereum/Solana wallet. Select the NEAR wallet // explicitly rather than treating the first linked wallet as a NEAR account. const nearWallet = user.linkedAccounts.find((account) => account.type === 'wallet' && account.chainType === 'near' ) if (nearWallet?.type !== 'wallet') { if (!creatingWallet.current) { creatingWallet.current = true createWallet({ chainType: 'near' }) .catch((error) => console.error('Could not create NEAR wallet', error)) .finally(() => { creatingWallet.current = false }) } return } const signer = new privySigner(signRawHash, nearWallet.address, provider) setNearAccount(new Account(nearWallet.address, provider, signer)) setWalletId(nearWallet.address) }, [authenticated, user, createWallet, signRawHash]) return ( <NearContext.Provider value={{ provider, nearAccount, walletId }}> {children} </NearContext.Provider> ) }
Value Type Description walletIdstringThe user's NEAR account address nearAccountAccountA near-api-jsAccountready to sign transactionsproviderJsonRpcProviderA read-only RPC provider to query the network
Custom signer
Privy signs through its embedded wallet via
signRawHash. TheprivySignerclass extendsSignerfromnear-api-jsand implementsgetPublicKeyandsignBytes. The SDK serializes and hashes transactions, delegate actions, and NEP-413 messages before requesting a signature:import { Account, PublicKey, Signer } from 'near-api-js' import type { JsonRpcProvider } from 'near-api-js' import type { useSignRawHash } from '@privy-io/react-auth/extended-chains' import { bytesToHex, hexToBytes } from '@noble/hashes/utils' // Keep Privy's embedded wallet as the signer. The SDK handles transaction, // delegate-action and NEP-413 serialization and hashing consistently. type SignRawHash = ReturnType<typeof useSignRawHash>['signRawHash'] export class privySigner extends Signer { private signRawHash: SignRawHash private nearAccId: string private provider: JsonRpcProvider constructor(signRawHash: SignRawHash, nearAccId: string, provider: JsonRpcProvider) { super() this.signRawHash = signRawHash this.nearAccId = nearAccId this.provider = provider } async getPublicKey() { if (/^[a-f0-9]{64}$/i.test(this.nearAccId)) { return new PublicKey({ keyType: 0, data: hexToBytes(this.nearAccId) }) } const account = new Account(this.nearAccId, this.provider) const { keys } = await account.getAccessKeyList() if (!keys.length) throw new Error('The account has no registered access key') return PublicKey.fromString(keys[0].public_key) } async signBytes(bytes: Uint8Array) { const { signature } = await this.signRawHash({ address: this.nearAccId, chainType: 'near', hash: `0x${bytesToHex(bytes)}`, }) return hexToBytes(signature.replace(/^0x/, '')) } }
Signing in and out
Use
useLoginanduseLogoutfrom Privy to open the login modal and sign the user out:import { useEffect, useState } from 'react' import NearLogo from '@/assets/near-logo.svg' import { Link } from 'react-router' import styles from '@/styles/app.module.css' import { useLogin, useLogout, usePrivy } from '@privy-io/react-auth' import { useNEAR } from '../context/useNear' import { NEAR } from 'near-api-js/tokens' export const Navigation = () => { const [action, setAction] = useState<() => void>(() => () => {}) const [label, setLabel] = useState('Loading...') const [balance, setBalance] = useState<string>('0') const { authenticated, user } = usePrivy() const { login: privyLogin } = useLogin() const { logout: privyLogout } = useLogout() const { walletId, nearAccount } = useNEAR() useEffect(() => { if (authenticated && user) { const userId = user.google?.email ?? user.email?.address ?? user.github?.username ?? 'User' setAction(() => privyLogout) setLabel(`Logout ${userId}`) } else { setAction(() => privyLogin) setLabel('Login') setBalance('0') } }, [authenticated, privyLogin, privyLogout, user]) useEffect(() => { if (walletId && nearAccount) { nearAccount .getBalance() .then((b) => setBalance(Number(NEAR.toDecimal(b)).toFixed(2))) .catch(() => setBalance('0')) // non existing account } }, [walletId, nearAccount]) return ( <> <nav className="navbar navbar-expand-lg"> <div className="container-fluid"> <Link to="/"> <img src={NearLogo} alt="NEAR" width="30" height="24" className={styles.logo} /> </Link> <div className="navbar-nav pt-1"> {authenticated ? ( <span className="badge text-dark small"> {' '} <br /> {walletId}: {balance} Ⓝ </span> ) : null} <button className="btn btn-secondary" onClick={action}> {label} </button> </div> </div> </nav> </> ) }
Call read-only methods
Use the
providerfrom context to call view methods — no login required:const greeting = await provider.callFunction({ contractId: CONTRACT, method: 'get_greeting', args: {}, })
Call change methods
Use the
nearAccountfrom context to call change methods. The transaction is signed silently by Privy's embedded wallet, providing a Web2-like UX:await nearAccount.callFunction({ contractId: CONTRACT, methodName: 'set_greeting', args: { greeting: newGreeting }, })
Get balance
import { NEAR } from 'near-api-js/tokens' const balance = await nearAccount.getBalance(NEAR) const formatted = Number(NEAR.toDecimal(balance)).toFixed(2) console.log(`Balance: ${formatted} Ⓝ`)