Build a Registration Flow

Build a complete identity registration flow using the useRegistration hook and its 12 progress phases.

Build a Registration Flow

In this tutorial we will build a complete identity registration UI using the useRegistration hook. A user will type an alias, click a button, and watch their registration progress through every step — from alias availability check all the way to on-chain confirmation. By the end you will have a full registration form that provides real-time feedback and displays the final transaction hash and DID when registration succeeds.


What we will build

A RegistrationForm component that:

  1. Accepts an alias from the user.
  2. Calls register(alias) from useRegistration.
  3. Displays a live progress list that updates as each of the 12 phases completes.
  4. Shows the registration result — DID, smart account address, and transaction hash — on success.

Prerequisites

  • You have completed the Quickstart tutorial. Your app already has HyperAuthProvider wrapping its root.

Steps

Understand the registration phases

Before writing any UI code, we will look at what useRegistration tracks under the hood. The hook moves through these 12 steps in order:

#LabelPhase value
0Checking availabilitychecking-alias
1Creating passkeycreating-passkey
2Generating identitygenerating-identity
3Predicting accountpredicting-account
4Checking account statefetching-account-state
5Preparing registrationcreating-registration
6Covering feessponsoring
7Granting permissionsauthorizing
8Signing securelysigning
9Submitting registrationsubmitting
10Waiting for confirmationconfirming
11Saving sessionstoring-session

Each step has a status of 'pending', 'active', 'done', or 'error'. We will use these to render a live checklist.

Create the RegistrationForm component

Create a new file src/RegistrationForm.tsx:

// src/RegistrationForm.tsx
import { useState } from 'react';
import { useRegistration } from '@hyperauth/react';

export function RegistrationForm() {
  const [alias, setAlias] = useState('');
  const { register, reset, phase, steps, identity, result, error, isRegistering } =
    useRegistration();

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!alias.trim()) return;
    await register(alias.trim());
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label htmlFor="alias">Choose an alias</label>
        <input
          id="alias"
          type="text"
          value={alias}
          onChange={(e) => setAlias(e.target.value)}
          placeholder="e.g. alice"
          disabled={isRegistering}
        />
        <button type="submit" disabled={isRegistering || !alias.trim()}>
          {isRegistering ? 'Registering...' : 'Register'}
        </button>
      </form>

      {steps.length > 0 && <ProgressList steps={steps} />}
      {result && <RegistrationResult result={result} />}
      {error && <ErrorMessage error={error} onRetry={reset} />}
    </div>
  );
}

useRegistration is called without arguments — it reads the client from HyperAuthProvider internally. The register(alias) function is the only call you need to make. Everything else — passkey creation, identity generation, on-chain submission — happens automatically.

Add the ProgressList component

Add this to the same file, below RegistrationForm:

// src/RegistrationForm.tsx (continued)
import type { RegistrationStep } from '@hyperauth/react';

function ProgressList({ steps }: { steps: RegistrationStep[] }) {
  return (
    <ol style={{ listStyle: 'none', padding: 0 }}>
      {steps.map((step, i) => (
        <li key={i} style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
          <StepIcon status={step.status} />
          <span>{step.label}</span>
          {step.detail && (
            <span style={{ color: 'grey', fontSize: '0.85em' }}>— {step.detail}</span>
          )}
        </li>
      ))}
    </ol>
  );
}

function StepIcon({ status }: { status: RegistrationStep['status'] }) {
  const icons: Record<RegistrationStep['status'], string> = {
    pending: '○',
    active: '◐',
    done: '●',
    error: '✕',
  };
  return <span aria-hidden="true">{icons[status]}</span>;
}

Each RegistrationStep has three fields:

interface RegistrationStep {
  label: string;                           // human-readable name
  status: 'pending' | 'active' | 'done' | 'error';
  detail?: string;                         // e.g. "Available", "Created", "0xabc..."
}

The detail field carries short contextual text that Hyperauth fills in as each step finishes — for example step 0 sets detail to 'Available' once the alias check passes, and step 3 sets it to the predicted smart account address.

Add the result and error components

Still in src/RegistrationForm.tsx:

// src/RegistrationForm.tsx (continued)
import type { RegistrationOutcome } from '@hyperauth/react';

function RegistrationResult({ result }: { result: RegistrationOutcome }) {
  return (
    <div>
      <h3>Registration complete</h3>
      <dl>
        <dt>DID</dt>
        <dd style={{ wordBreak: 'break-all' }}>{result.did}</dd>

        <dt>Smart account</dt>
        <dd style={{ wordBreak: 'break-all' }}>{result.smartAccount}</dd>

        <dt>Transaction</dt>
        <dd style={{ wordBreak: 'break-all' }}>{result.txHash}</dd>

        {result.blockNumber !== null && (
          <>
            <dt>Block</dt>
            <dd>{result.blockNumber}</dd>
          </>
        )}
      </dl>
    </div>
  );
}

function ErrorMessage({ error, onRetry }: { error: string; onRetry: () => void }) {
  return (
    <div role="alert">
      <p>{error}</p>
      <button onClick={onRetry}>Try again</button>
    </div>
  );
}

RegistrationOutcome is the shape of the result value returned by the hook once registration completes:

interface RegistrationOutcome {
  did: string;
  txHash: string;
  blockNumber: number | null;
  smartAccount: string;
}

Mount the form in your app

Open src/App.tsx and add the form inside the ready branch:

// src/App.tsx
import { useHyperAuth } from '@hyperauth/react';
import { RegistrationForm } from './RegistrationForm';

export default function App() {
  const { status } = useHyperAuth();

  if (status === 'initializing') return <p>Loading...</p>;
  if (status === 'error') return <p>Failed to initialise Hyperauth.</p>;

  return (
    <main>
      <h1>Create your identity</h1>
      <RegistrationForm />
    </main>
  );
}

Run the flow end to end

Start the dev server and open the app:

npm run dev

Type an alias — for example alice — and click Register. You'll see the progress list come alive one row at a time:

● Checking availability  — Available
● Creating passkey       — Created
◐ Generating identity
○ Predicting account
○ Checking account state
...

Follow the browser's WebAuthn prompt when step 1 appears. After step 10 confirms on-chain, the result panel appears with your DID, smart account address, and transaction hash.

Notice that if the alias is already taken, the flow stops at step 0 with an error message and the Try again button resets everything so the user can pick a different name.


What you have built

You've built a complete identity registration flow. useRegistration manages all twelve steps of the process — alias checking, passkey creation, identity generation, account prediction, fee sponsorship, signing, on-chain submission, and session storage — and exposes just enough state for your UI to reflect exactly what is happening at every moment.

The result.did and result.smartAccount values you received are now live on-chain. The next tutorial, Sign and Verify Data, shows how to use the identity you just created to sign arbitrary messages.

On this page