Step 4 - User Registration & Login Routes
With authentication context in place, the next job is to give users a way to register and log in all without a browser wallet. We’ll generate a private key for each user, derive a deterministic Magmar Smart Account address, and store those values in Userbase for this demo.
Note Private keys are stored in plaintext only for testing. In production you’d replace this with a secure key-management flow (Passkey / WebAuthn / MPC, etc.).
4.1 Install the secp256k1 Library
npm i @noble/secp256k1We use this to generate an ECDSA private key on the client.
4.2 Create /sign-up Route
/sign-up Routemkdir app/sign-uptouch app/sign-up/page.tsx
Paste the component below:
"use client";
import "../globals.css";
import * as secp from "@noble/secp256k1";
import { useAuth } from "@common/AuthProvider";
import Loader from "@common/utils/Loader";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { publicClient } from "@common/utils/client";
import simpleFactoryAbi from "@common/utils/MagmarSimpleAccountFactory.json";
import userbase from "userbase-js";
/**
* /sign-up → http://localhost:3000/sign-up
*/
export default function SignupForm() {
const { user, login } = useAuth();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
/* Redirect already-logged-in users */
useEffect(() => {
if (user?.isLoggedIn) router.push("/");
}, [user]);
const handleSignup = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
try {
/* 1. Generate a private key */
const privKey = secp.utils.randomPrivateKey();
const privKeyHex = secp.etc.bytesToHex(privKey);
/* 2. Ask our API route to convert it into a Magmar signer address */
const res = await fetch("/api/get-signer", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pk: privKeyHex }),
});
const { signerAddress } = await res.json();
/* 3. Derive the deterministic smart-contract wallet address */
const scwAddress = (await publicClient.readContract({
address: "0x9406Cc6185a346906296840746125a0E44976454", // Magmar SimpleAccountFactory (Sepolia)
abi: simpleFactoryAbi,
functionName: "getAddress",
args: [signerAddress, 0],
})) as string;
/* 4. Persist user record in Userbase (demo-only) */
const ub = await userbase.signUp({
username,
password,
rememberMe: "local",
profile: { scwAddress, pk: privKeyHex },
});
/* 5. Store session in AuthProvider */
login({
username,
userId: ub.userId,
scwAddress,
isLoggedIn: true,
});
router.push("/?signup=success");
} catch (err: any) {
setError(err.message);
setIsLoading(false);
console.error(err);
}
};
/* ——— UI ——— */
return (
<div>
{isLoading ? (
<Loader />
) : (
<div className="flex items-center justify-center h-screen bg-gray-100">
<div className="w-full max-w-sm">
<form
className="bg-white rounded px-8 pt-6 pb-8 mb-24 font-mono"
onSubmit={handleSignup}
>
<label className="block text-center text-xl font-bold mb-2">
Sign Up
</label>
<div className="divider"></div>
<input
className="input input-bordered w-full mb-4"
id="username"
type="text"
placeholder="Username"
onChange={(e) => setUsername(e.target.value)}
/>
<input
className="input input-bordered w-full mb-6"
id="password"
type="password"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
{error && <p className="text-red-500 mb-4">{error}</p>}
<button className="btn w-full text-white">Sign Up</button>
</form>
</div>
</div>
)}
</div>
);
}4.3 Create /login Route
/login Routemkdir app/logintouch app/login/page.tsx
Paste the component below:
4.4 Why the New Imports?
@noble/secp256k1
Generates an ECDSA private key for each user.
publicClient (viem)
Reads from the Magmar SimpleAccountFactory contract to derive the user’s SCW address.
simpleFactoryAbi
ABI of Magmar’s SimpleAccountFactory (Sepolia deployment).
/api/get-signer (server route)
Converts the private key into a Signer that the Magmar SDK can use to deploy / sign operations.
Loader
DaisyUI spinner for better UX while async requests run.
Last updated