@rootherald/react
A small React wrapper around @rootherald/browser. Keyless: it collects a hardware-rooted evidence blob over a backend-issued nonce and hands it to your callback — it holds no secret and never renders a verdict. Your backend relays the blob to the verify API, which appraises it.
This SDK is planned and not yet available. Until it ships, mint attestation tokens on the device and verify them server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.
Install
npm install @rootherald/react @rootherald/browserProvider setup
Wrap your app once. The provider configures the underlying keyless browser collector and exposes hooks to the rest of your tree. The publishable key is for site/origin attribution only — never a secret.
"use client";
import { RootHeraldProvider } from "@rootherald/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<RootHeraldProvider siteKey="rh_pk_live_REPLACE_ME">
{children}
</RootHeraldProvider>
);
}<RootHeraldGate> — the drop-in form gate
The gate collects an evidence blob over a nonce your backend issued, then hands the blob to your onEvidence callback. The nonce comes from your backend (which called POST /api/v1/attestations/challenge with its rh_sk_ secret), and your backend relays the blob to POST /api/v1/attestations/verify and enforces the verdict. The verdict never travels through the client.
import { RootHeraldGate } from "@rootherald/react";
export function SignupForm() {
return (
<RootHeraldGate
// getNonce hits YOUR backend, which owns the rh_sk_ secret and the verdict.
getNonce={() => myBackend.getChallengeNonce("signup")}
onEvidence={async (evidence) => {
// Relay the opaque blob to your backend, which POSTs it to
// /api/v1/attestations/verify and enforces the verdict.
await fetch("/api/signup", {
method: "POST",
body: JSON.stringify({ evidence, ...formState }),
});
}}
onDeclined={() => alert("Please complete device verification to sign up.")}
>
{({ loading, ready, collect }) => (
<button onClick={collect} disabled={!ready || loading}>
{loading ? "Collecting evidence…" : "Create account"}
</button>
)}
</RootHeraldGate>
);
}We use a render-prop instead of a wrapped button so you keep full control of the button's markup and your design system. ready is false until the collector has detected the platform capability; loading is true while evidence collection is in flight.
Hooks
useRootHerald()returns{ collect, isAvailable }.collect(nonce)walks the keyless@rootherald/browsercollector and resolves to the opaque evidence blob for you to relay — it never returns a token, deviceId, or verdict.useClientStatus()returns the collector's local readiness (enrolled / not enrolled / unsupported) so you can gate UI. It reports no device class or verdict — those live only in the verify response on your backend.