NERO
Guides

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

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

MethodConfig
password{ password: "..." }
recovery_phrase{ phrase: "..." }
deviceDevice-specific config
social_oauthOAuth provider credentials
email_backupEmail-based verification
guardianGuardian-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

ScenarioMechanismNetwork requiredOutput
Lost device, NERO onlineAccount recovery flow (initiate / verify / complete)YesRestored key share, MPC resumes
Cleared browser storageBackup import (importBackup)YesRestored key share, MPC resumes
NERO backend permanently downOffline reconstruction (offlineRecover)NoFull private key for import into MetaMask

What the user sees

StateUI
Exporting backupButton disabled, "Exporting..." text
Backup readyJSON blob displayed for the user to save
Importing backupButton disabled, "Importing..." text
Import completeConfirmation message
Self-custody setupButton disabled during setup, factor ID on success
Offline reconstructionPrivate key and wallet address displayed
Recovery in progressStep-by-step flow: initiate, verify, complete
Error (wrong password, invalid blob)Error message from hook

Hooks reference

HookKey methods
useNeroBackupexportBackup(password), importBackup(backup, password), getInfo()
useNeroRecoverysetupSelfCustody(password), offlineRecover(json, password), setup(), initiate(), verify(), complete(), cancel()

On this page