React bindings for the Me2em protocol: identity lifecycle, seed phrase UX, and hierarchical handle derivation.
pnpm add @me2em/react react
Requires React >= 18.0.0 as a peer dependency.
💡 This package requires
@me2em/core(peer dependency via workspace) and React ≥ 18. It is self-contained — for E2EE channels, X3DH, and Argon2id see@me2em/crypto. All three packages are designed to work together — see the root README for the full package map.
import { Me2emProvider, useCreateIdentity } from '@me2em/react';
function App() {
return (
<Me2emProvider>
<CreateIdentityScreen />
</Me2emProvider>
);
}
function CreateIdentityScreen() {
const { status, seedWords, generate, confirmWords, reset } = useCreateIdentity();
if (status === 'idle') {
return <button onClick={() => generate()}>Create new identity</button>;
}
if (status === 'seed-generated' && seedWords) {
return (
<>
<p>Write down your seed phrase:</p>
<SeedPhraseDisplay words={seedWords} />
<button onClick={confirmWords}>I have saved it</button>
</>
);
}
return <p>Identity ready</p>;
}
The package has two strict layers:
src/headless/ — Framework-agnostic logic: identity flow state machines, seed word utilities, validation. This layer never imports React. It can be tested without jsdom and will eventually become @me2em/sdk.src/react/ — React hooks and components that compose headless logic with React state and context.Headless exports are available under the headless namespace:
import { headless } from '@me2em/react';
const state = headless.identityFlowReducer(headless.initialIdentityFlowState, {
type: 'GENERATE',
words: ['abandon', ...],
seed: new Uint8Array(32),
});
Protocol terms are domain-neutral. The terms most relevant to this package:
| Term | Definition |
|---|---|
| Identity | Root cryptographic identity derived from a seed phrase. Constant forever. |
| Handle | A derived, attested, named key representing a distinct identity context. |
| Session | A stateless signed authorization token. |
| Transient material | Key bytes existing only in RAM at the moment of use. |
| Context cache | Per-identity encrypted storage of derived Handle context. |
Full glossary (including Attestation, SubHandle, Ephemeral material): see the root README.
This package never persists your seed. The seed phrase exists in two places only — your paper and transient memory during login. What this package stores is derived, encrypted context caches. Losing your browser data never loses your identity, because your identity lives in your head.
Shows the seed phrase once. The user reveals the words, can copy them (allowed exactly once), then dismisses. After dismissal the phrase is hidden and cannot be re-revealed.
<SeedPhraseDisplay
words={seedWords}
onDismiss={() => setRevealed(false)}
/>
Order-verification component: the user must click the seed phrase words in the correct order among decoy words in a grid. Auto-checks when all words are selected; shows "Try again" on failure.
<SeedPhraseVerify
realWords={seedWords}
onVerified={() => confirmWords()}
onFailed={(attempts) => console.log(`Failed after ${attempts} attempts`)}
/>
Step-by-step seed phrase import with 12/24 word toggle, per-word validation against the BIP39 wordlist, and automatic checksum check.
<SeedPhraseImport
expectedCount={12}
onImported={(words) => importWords(words)}
/>
BIP39 passphrase input with optional security warning. The passphrase is NFKC-normalized and case-sensitive — a different passphrase silently derives a different wallet.
<PassphraseInput
onPassphraseChange={(p) => setPassphrase(p)}
showWarnings={true}
/>
The useSession hook creates and auto-renews signed session tokens for a Handle.
import { useHandle, useSession } from '@me2em/react';
function ChatScreen() {
const handle = useHandle('alice');
const { session, isExpired, renew } = useSession(handle, {
audience: 'chat.example.com',
scopes: ['send', 'receive'],
ttl: 3600,
});
if (isExpired) {
return <button onClick={renew}>Renew session</button>;
}
return <p>Session valid until {new Date(session!.expiresAt * 1000)}</p>;
}
The hook auto-renews at half the session TTL by default. Set autoRenew: false to disable.
The Identity Context Cache provides per-identity encrypted storage for Handle context data using IndexedDB with WebCrypto AES-GCM encryption.
import {
IdentityContextIDBStorage,
useIdentityContext,
deriveCacheKey,
} from '@me2em/react';
function ContextPanel() {
const storage = useMemo(() => new IdentityContextIDBStorage(), []);
// At login time, derive and set the cache key:
useEffect(() => {
const salt = crypto.getRandomValues(new Uint8Array(32));
deriveCacheKey(identitySeed, salt).then((key) => {
storage.setCacheKey(key);
});
}, []);
const { context, save, clear, refresh } = useIdentityContext(storage);
if (!context) {
return <p>No context loaded</p>;
}
return (
<div>
<p>Identity: {context.identityId}</p>
<p>Saved: {new Date(context.savedAt)}</p>
<button onClick={() => save({ ...context, savedAt: Date.now() })}>
Save
</button>
<button onClick={clear}>Clear</button>
</div>
);
}
Each identity gets a separate IndexedDB database (name = SHA-256 hash of identity ID), guaranteeing full isolation between identities.
Alpha. API may change between 0.1.x releases. See packages/core/README.md for the underlying protocol.
Roadmap: @me2em/crypto protocol mechanisms — planned