Login + Create a Wallet
Authenticate users via Google or GitHub OAuth and generate an MPC wallet — complete React walkthrough
Login + Create a Wallet
This guide walks through the full OAuth login flow: provider setup, social login buttons, callback handling, wallet generation, and displaying the wallet address.
Prerequisites
@nerochain/mpc-sdkinstalled (Installation)- A NERO project with an API key and OAuth providers configured (Dashboard)
Step 1 — Wrap your app with the provider
The NeroMpcAuthProvider initializes the SDK and makes all hooks available to child components. Place it at the root of your component tree.
import { NeroMpcAuthProvider } from "@nerochain/mpc-sdk/react";
import type { SDKConfig } from "@nerochain/mpc-sdk";
const config: SDKConfig = {
backendUrl: import.meta.env.VITE_NERO_AUTH_URL,
apiKey: import.meta.env.VITE_NERO_API_KEY,
protocol: "dkls",
uiConfig: {
appName: "My App",
},
};
function Root() {
return (
<NeroMpcAuthProvider config={config} autoConnect>
<App />
</NeroMpcAuthProvider>
);
}autoConnect tells the SDK to restore an existing session on page load. If a user previously logged in and their tokens are still valid, they skip straight to the authenticated state.
Step 2 — Add login buttons
useNeroConnect exposes a connect() method that redirects the browser to the OAuth provider's authorization page. Before redirecting, store the provider name so the callback handler knows which provider to use.
import { useNeroConnect } from "@nerochain/mpc-sdk/react";
import { useCallback } from "react";
type Provider = "google" | "github";
function LoginButtons() {
const { connect } = useNeroConnect();
const handleLogin = useCallback(
async (provider: Provider) => {
try {
localStorage.setItem("nero:oauth_provider", provider);
await connect(provider, window.location.origin + "/");
} catch {
localStorage.removeItem("nero:oauth_provider");
}
},
[connect],
);
return (
<div>
<button onClick={() => handleLogin("google")}>Login with Google</button>
<button onClick={() => handleLogin("github")}>Login with GitHub</button>
</div>
);
}The second argument to connect() is the redirect URI. The OAuth provider will send the user back to this URL with code and state query parameters.
Step 3 — Handle the OAuth callback
After the provider redirects back, you need to detect the code and state URL parameters, exchange them for a session, and optionally trigger wallet generation.
Mount this component at the root level so it runs on every page load:
import {
useNeroConnect,
useNeroMpcAuth,
useNeroWallet,
} from "@nerochain/mpc-sdk/react";
import { useEffect, useRef, useState } from "react";
function OAuthCallbackHandler() {
const { sdk, isLoading } = useNeroMpcAuth();
const { handleCallback } = useNeroConnect();
const { generateWallet } = useNeroWallet();
const [status, setStatus] = useState<string | null>(null);
const processing = useRef(false);
useEffect(() => {
if (isLoading || !sdk || processing.current) return;
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const state = params.get("state");
if (!code || !state) return;
processing.current = true;
const provider = localStorage.getItem("nero:oauth_provider") ?? "google";
localStorage.removeItem("nero:oauth_provider");
window.history.replaceState({}, "", window.location.pathname);
(async () => {
try {
setStatus("Completing login...");
const result = await handleCallback(
provider as Parameters<typeof handleCallback>[0],
code,
state,
window.location.origin + "/",
);
if (result.requiresDKG) {
setStatus("Generating wallet (DKG)...");
await generateWallet();
}
setStatus(null);
} catch (err) {
setStatus(
`Error: ${err instanceof Error ? err.message : String(err)}`,
);
setTimeout(() => setStatus(null), 8000);
}
})();
}, [sdk, isLoading, handleCallback, generateWallet]);
if (!status) return null;
return (
<div
style={{
position: "fixed",
top: 16,
left: "50%",
transform: "translateX(-50%)",
zIndex: 50,
padding: "8px 16px",
borderRadius: 8,
background: status.startsWith("Error:") ? "#fee" : "#eef",
color: status.startsWith("Error:") ? "#900" : "#006",
fontSize: 14,
}}
>
{status}
</div>
);
}Key details:
processingref prevents double-execution in React StrictModewindow.history.replaceStatestrips the OAuth params from the URL so a page refresh does not re-trigger the flowrequiresDKGistruefor first-time users who need a new MPC wallet- The redirect URI passed to
handleCallbackmust match the one used inconnect()
Step 4 — Display wallet status and trigger generation
After authentication, check whether the user already has a wallet. If not, show a button to generate one. generateWallet() runs the DKLS distributed key generation protocol between the browser and the NERO backend.
import {
useNeroConnect,
useNeroUser,
useNeroWallet,
} from "@nerochain/mpc-sdk/react";
import { useCallback, useState } from "react";
type Provider = "google" | "github";
function LoginPage() {
const { connect } = useNeroConnect();
const { user, isAuthenticated } = useNeroUser();
const { wallet, hasWallet, generateWallet, isGenerating, error } =
useNeroWallet();
const [genResult, setGenResult] = useState<unknown>(null);
const handleLogin = useCallback(
async (provider: Provider) => {
try {
localStorage.setItem("nero:oauth_provider", provider);
await connect(provider, window.location.origin + "/");
} catch {
localStorage.removeItem("nero:oauth_provider");
}
},
[connect],
);
const handleGenerate = async () => {
try {
const result = await generateWallet();
setGenResult(result);
} catch {
// error state is surfaced via the hook's `error` property
}
};
if (!isAuthenticated) {
return (
<div>
<p>Login with a social provider:</p>
<button onClick={() => handleLogin("google")}>Google</button>
<button onClick={() => handleLogin("github")}>GitHub</button>
</div>
);
}
return (
<div>
<p>Logged in as {user?.displayName ?? user?.email ?? user?.id}</p>
{hasWallet ? (
<p>Wallet: {wallet?.eoaAddress}</p>
) : (
<button onClick={handleGenerate} disabled={isGenerating}>
{isGenerating ? "Generating wallet (DKG)..." : "Generate Wallet"}
</button>
)}
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}Complete example
Putting it all together with the provider, callback handler, and login page:
import { NeroMpcAuthProvider } from "@nerochain/mpc-sdk/react";
import type { SDKConfig } from "@nerochain/mpc-sdk";
import {
useNeroConnect,
useNeroMpcAuth,
useNeroUser,
useNeroWallet,
} from "@nerochain/mpc-sdk/react";
import { useCallback, useEffect, useRef, useState } from "react";
const config: SDKConfig = {
backendUrl: import.meta.env.VITE_NERO_AUTH_URL,
apiKey: import.meta.env.VITE_NERO_API_KEY,
protocol: "dkls",
};
type Provider = "google" | "github";
function OAuthCallbackHandler() {
const { sdk, isLoading } = useNeroMpcAuth();
const { handleCallback } = useNeroConnect();
const { generateWallet } = useNeroWallet();
const [status, setStatus] = useState<string | null>(null);
const processing = useRef(false);
useEffect(() => {
if (isLoading || !sdk || processing.current) return;
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const state = params.get("state");
if (!code || !state) return;
processing.current = true;
const provider = localStorage.getItem("nero:oauth_provider") ?? "google";
localStorage.removeItem("nero:oauth_provider");
window.history.replaceState({}, "", window.location.pathname);
(async () => {
try {
setStatus("Completing login...");
const result = await handleCallback(
provider as Parameters<typeof handleCallback>[0],
code,
state,
window.location.origin + "/",
);
if (result.requiresDKG) {
setStatus("Generating wallet (DKG)...");
await generateWallet();
}
setStatus(null);
} catch (err) {
setStatus(
`Error: ${err instanceof Error ? err.message : String(err)}`,
);
setTimeout(() => setStatus(null), 8000);
}
})();
}, [sdk, isLoading, handleCallback, generateWallet]);
if (!status) return null;
return (
<div
style={{
position: "fixed",
top: 16,
left: "50%",
transform: "translateX(-50%)",
padding: "8px 16px",
borderRadius: 8,
background: status.startsWith("Error:") ? "#fee" : "#eef",
}}
>
{status}
</div>
);
}
function LoginPage() {
const { connect } = useNeroConnect();
const { user, isAuthenticated } = useNeroUser();
const { wallet, hasWallet, generateWallet, isGenerating, error } =
useNeroWallet();
const handleLogin = useCallback(
async (provider: Provider) => {
try {
localStorage.setItem("nero:oauth_provider", provider);
await connect(provider, window.location.origin + "/");
} catch {
localStorage.removeItem("nero:oauth_provider");
}
},
[connect],
);
const handleGenerate = async () => {
try {
await generateWallet();
} catch {
// error surfaced via hook
}
};
if (!isAuthenticated) {
return (
<div>
<h1>Welcome</h1>
<button onClick={() => handleLogin("google")}>
Login with Google
</button>
<button onClick={() => handleLogin("github")}>
Login with GitHub
</button>
</div>
);
}
return (
<div>
<p>Hello, {user?.displayName ?? user?.email}</p>
{hasWallet ? (
<p>Wallet: {wallet?.eoaAddress}</p>
) : (
<button onClick={handleGenerate} disabled={isGenerating}>
{isGenerating ? "Generating..." : "Create Wallet"}
</button>
)}
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}
export default function App() {
return (
<NeroMpcAuthProvider config={config} autoConnect>
<OAuthCallbackHandler />
<LoginPage />
</NeroMpcAuthProvider>
);
}What the user sees
| State | UI |
|---|---|
| Not logged in | Login buttons for Google / GitHub |
| OAuth redirect in progress | Browser navigates to provider consent screen |
| Callback processing | Toast: "Completing login..." |
| First-time user (DKG) | Toast: "Generating wallet (DKG)..." (takes 2-5 seconds) |
| Authenticated with wallet | Greeting + wallet address |
| Error (invalid code, network) | Red toast with error message, auto-dismisses after 8 seconds |
Flow summary
connect(provider, redirectUri)redirects to the OAuth provider- Provider redirects back with
codeandstatequery parameters handleCallback(provider, code, state, redirectUri)exchanges the code for a session- If
result.requiresDKGistrue, callgenerateWallet()to run distributed key generation - The wallet address is available via
useNeroWallet().wallet.eoaAddress