Recover a Wallet
Export encrypted backups, set up recovery methods, and reconstruct your private key offline
Recover a Wallet
The NERO MPC SDK provides two complementary safety nets: encrypted backups for preserving your key share, and account recovery for regaining access through a multi-step verified flow. A third mechanism — self-custody recovery — lets you reconstruct the full private key offline if the NERO backend ever goes permanently down.
Prerequisites
- User is authenticated with a generated wallet (Login + Create a Wallet)
@nerochain/mpc-sdkinstalled
Encrypted backup
When to set up a backup
Set up a backup immediately after the first wallet generation. The backup captures your client key share encrypted with a password you choose. If you lose your device or clear browser storage, you can restore access by importing the backup.
Export a backup
useNeroBackup handles export, import, and status queries. exportBackup() encrypts your key share with scrypt KDF + AES-256-GCM and returns a backup blob plus a fingerprint for identification.
import { useNeroBackup } from "@nerochain/mpc-sdk/react";
import { useState } from "react";
function ExportBackup() {
const { exportBackup, isLoading, error } = useNeroBackup();
const [backupData, setBackupData] = useState<string | null>(null);
const handleExport = async () => {
try {
const { backup, fingerprint } = await exportBackup("MySecureBackup123!");
setBackupData(JSON.stringify(backup, null, 2));
console.log("Backup fingerprint:", fingerprint);
} catch {
// error state is surfaced via the hook's `error` property
}
};
return (
<div>
<button onClick={handleExport} disabled={isLoading}>
{isLoading ? "Exporting..." : "Export Backup"}
</button>
{backupData && (
<div>
<p>Store this data safely (cloud storage, USB drive, password manager):</p>
<textarea
readOnly
value={backupData}
rows={10}
style={{ width: "100%", fontFamily: "monospace", fontSize: 12 }}
/>
</div>
)}
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}The password must be at least 12 characters with uppercase, lowercase, digit, and special character. Choose a strong password and store it separately from the backup blob.
Import a backup
If you lose your key share (new device, cleared storage), import the backup to restore it:
import { useNeroBackup } from "@nerochain/mpc-sdk/react";
import { useState } from "react";
function ImportBackup() {
const { importBackup, isLoading, error } = useNeroBackup();
const [backupJson, setBackupJson] = useState("");
const [password, setPassword] = useState("");
const [restored, setRestored] = useState(false);
const handleImport = async () => {
if (!password || !backupJson) return;
try {
const backup = JSON.parse(backupJson);
const result = await importBackup(backup, password);
console.log("Restored share for party:", result.partyId);
setRestored(true);
} catch {
// error state is surfaced via the hook's `error` property
}
};
return (
<div>
<textarea
value={backupJson}
onChange={(e) => setBackupJson(e.target.value)}
placeholder="Paste your backup JSON here"
rows={6}
style={{ width: "100%", fontFamily: "monospace", fontSize: 12 }}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Backup password"
/>
<button onClick={handleImport} disabled={isLoading || !password || !backupJson}>
{isLoading ? "Importing..." : "Import Backup"}
</button>
{restored && <p>Key share restored. You can sign transactions again.</p>}
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}Check backup status
const { getInfo, info } = useNeroBackup();
await getInfo();
console.log(info); // { hasBackup: true, lastBackupAt: "...", ... }Self-custody recovery (offline key reconstruction)
Self-custody recovery is a separate mechanism designed for the worst case: the NERO backend goes permanently offline. It produces a composite blob containing both encrypted shares, which can reconstruct the full private key without any network access.
Set up self-custody recovery
After generating a wallet, call setupSelfCustody() to create the composite blob. The SDK encrypts your client share, fetches the server share via ECDH, and bundles everything into a password-protected blob stored in the factor API.
import { useNeroRecovery } from "@nerochain/mpc-sdk/react";
import { useState } from "react";
function SetupSelfCustody() {
const { setupSelfCustody, isLoading, error } = useNeroRecovery();
const [factorId, setFactorId] = useState<string | null>(null);
const handleSetup = async () => {
try {
const { factorId } = await setupSelfCustody("MyRecoveryPassword123!");
setFactorId(factorId);
} catch {
// error state is surfaced via the hook's `error` property
}
};
return (
<div>
<button onClick={handleSetup} disabled={isLoading}>
{isLoading ? "Setting up..." : "Enable Self-Custody Recovery"}
</button>
{factorId && (
<p>
Self-custody recovery enabled. Factor ID: <code>{factorId}</code>
</p>
)}
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}Reconstruct the private key offline
If the backend is permanently unavailable, use offlineRecover() with the composite blob JSON and your password. This function operates entirely client-side with zero network access.
import { useNeroRecovery } from "@nerochain/mpc-sdk/react";
import { useState } from "react";
function OfflineRecovery() {
const { offlineRecover, isLoading, error } = useNeroRecovery();
const [compositeJson, setCompositeJson] = useState("");
const [password, setPassword] = useState("");
const [result, setResult] = useState<{
privateKey: string;
walletAddress: string;
} | null>(null);
const handleRecover = async () => {
try {
const recovered = await offlineRecover(compositeJson, password);
setResult(recovered);
} catch {
// error state is surfaced via the hook's `error` property
}
};
return (
<div>
<textarea
value={compositeJson}
onChange={(e) => setCompositeJson(e.target.value)}
placeholder="Paste your composite blob JSON here"
rows={8}
style={{ width: "100%", fontFamily: "monospace", fontSize: 12 }}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Recovery password (min 12 characters)"
/>
<button onClick={handleRecover} disabled={isLoading || !compositeJson || !password}>
{isLoading ? "Reconstructing..." : "Reconstruct Private Key"}
</button>
{result && (
<div>
<p>Wallet address: <code>{result.walletAddress}</code></p>
<p>Private key: <code>{result.privateKey}</code></p>
<p>
Import this private key into MetaMask or any standard
Ethereum wallet to access your funds.
</p>
</div>
)}
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}The offlineRecover function:
- Requires no SDK initialization and no network access
- Can be called before login
- Derives the encryption key from your password using scrypt
- Decrypts both client and server shares
- Combines them to produce the full private key
- Optionally verifies against an expected wallet address
Account recovery (multi-step verified flow)
For scenarios where you lose device access but the NERO backend is operational, the SDK offers a structured recovery flow with multiple method types and a time-lock for security.
Supported recovery methods
| Method | Config |
|---|---|
password | { password: "..." } |
recovery_phrase | { phrase: "..." } |
device | Device-specific config |
social_oauth | OAuth provider credentials |
email_backup | Email-based verification |
guardian | Guardian-based approval |
Step 1 — Set up a recovery method
Register a recovery method while you still have access:
import { useNeroRecovery } from "@nerochain/mpc-sdk/react";
function SetupRecovery() {
const { setup, isLoading, error } = useNeroRecovery();
const handleSetup = async () => {
try {
const result = await setup("password", {
password: "MyRecoveryPass123!",
});
console.log("Recovery method created:", result);
} catch {
// error state is surfaced via the hook's `error` property
}
};
return (
<button onClick={handleSetup} disabled={isLoading}>
{isLoading ? "Setting up..." : "Setup Password Recovery"}
</button>
);
}Step 2 — List configured methods
Before initiating recovery, check which methods are available:
const { listMethods, methods } = useNeroRecovery();
await listMethods();
methods.forEach((m) => {
console.log(`${m.methodType} — ${m.id}`);
});Step 3 — Initiate, verify, and complete recovery
The recovery flow follows a state machine: initiate -> verify -> complete. A 48-hour time-lock protects against unauthorized recovery attempts.
import { useNeroRecovery } from "@nerochain/mpc-sdk/react";
import { useState } from "react";
function RecoveryFlow() {
const { initiate, verify, complete, cancel, isLoading, error } =
useNeroRecovery();
const [attemptId, setAttemptId] = useState("");
const [verificationCode, setVerificationCode] = useState("");
const [step, setStep] = useState<"initiate" | "verify" | "complete">(
"initiate",
);
const [result, setResult] = useState<unknown>(null);
const handleInitiate = async (methodId: string) => {
try {
const res = await initiate(methodId);
setAttemptId(res.attemptId ?? "");
setStep("verify");
} catch {
// error state is surfaced via the hook's `error` property
}
};
const handleVerify = async () => {
try {
await verify(attemptId, verificationCode);
setStep("complete");
} catch {
// error state is surfaced via the hook's `error` property
}
};
const handleComplete = async () => {
try {
const res = await complete(attemptId);
setResult(res);
} catch {
// error state is surfaced via the hook's `error` property
}
};
const handleCancel = async () => {
try {
await cancel(attemptId);
setStep("initiate");
setAttemptId("");
} catch {
// error state is surfaced via the hook's `error` property
}
};
return (
<div>
{step === "initiate" && (
<button
onClick={() => handleInitiate("your-method-id")}
disabled={isLoading}
>
Start Recovery
</button>
)}
{step === "verify" && (
<div>
<p>Enter the verification code sent to you:</p>
<input
type="text"
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
placeholder="Verification code"
/>
<button onClick={handleVerify} disabled={isLoading || !verificationCode}>
Verify
</button>
<button onClick={handleCancel} disabled={isLoading}>
Cancel
</button>
</div>
)}
{step === "complete" && (
<button onClick={handleComplete} disabled={isLoading}>
Complete Recovery
</button>
)}
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
{error && <p style={{ color: "red" }}>{error.message}</p>}
</div>
);
}Recovery strategy summary
| Scenario | Mechanism | Network required | Output |
|---|---|---|---|
| Lost device, NERO online | Account recovery flow (initiate / verify / complete) | Yes | Restored key share, MPC resumes |
| Cleared browser storage | Backup import (importBackup) | Yes | Restored key share, MPC resumes |
| NERO backend permanently down | Offline reconstruction (offlineRecover) | No | Full private key for import into MetaMask |
What the user sees
| State | UI |
|---|---|
| Exporting backup | Button disabled, "Exporting..." text |
| Backup ready | JSON blob displayed for the user to save |
| Importing backup | Button disabled, "Importing..." text |
| Import complete | Confirmation message |
| Self-custody setup | Button disabled during setup, factor ID on success |
| Offline reconstruction | Private key and wallet address displayed |
| Recovery in progress | Step-by-step flow: initiate, verify, complete |
| Error (wrong password, invalid blob) | Error message from hook |
Hooks reference
| Hook | Key methods |
|---|---|
useNeroBackup | exportBackup(password), importBackup(backup, password), getInfo() |
useNeroRecovery | setupSelfCustody(password), offlineRecover(json, password), setup(), initiate(), verify(), complete(), cancel() |