Recover Wallet

Use recoverWallet() to restore lost MPC signing shares, recover wallet access, and securely migrate users to new devices.

Recovery reconstructs a client's signing shares from their backup. It requires the client's encrypted backup share and Tatum's custodian backup share — both are combined by the enclave to produce new signing shares.

When Recovery Is Needed

  • The user lost their device (and their client signing shares)
  • The user is migrating to a new device
  • The application lost the stored signing shares

Step 1: Retrieve the Encrypted Client Backup Share

If stored with Portal

// Get the backupSharePairId from ClientDetails
const details = await client.getClientDetails();
const secp256k1BackupPair = details.wallets
  ?.find(w => w.curve === "SECP256K1")
  ?.backupSharePairs?.[0];

const response = await client.getBackupShareCipherText({
  path: { backupSharePairId: secp256k1BackupPair!.id! },
});

const encryptedShare = response.cipherText;

If stored in your own backend

Retrieve the ciphertext and backupSharePairId from your database using the user's ID.

Step 2: Decrypt the Backup Share

Decrypt using the same method and key used during backup:

const share = decryptShare(encryptedShare, userKey);
// share is the plaintext backup share string

See Encrypt Shares for the decryptShare implementation.

Step 3: Recover Signing Shares

Pass the decrypted backup share (as a backup response object, JSON-stringified) to recoverWallet():

// The backup response structure matches backupWallet()'s return shape
// You need to provide both SECP256K1 and ED25519 shares
const backupResponse = {
  SECP256K1: { share: decryptedSecp256k1Share, id: secp256k1BackupPairId },
  ED25519:   { share: decryptedEd25519Share,   id: ed25519BackupPairId },
};

const newShares = await client.recoverWallet({
  body: {
    backupResponse: JSON.stringify(backupResponse),
  },
});

// newShares.SECP256K1 → new signing share
// newShares.ED25519   → new signing share

The recovered shares are new MPC signing shares — functionally equivalent to the originals, but different strings.

Step 4: Store the New Signing Shares

Encrypt and persist the new shares exactly as you did after the original generateWallet():

await db.updateShares(userId, {
  secp256k1Share: encryptShare(newShares.SECP256K1.share, encryptionKey),
  ed25519Share:   encryptShare(newShares.ED25519.share,   encryptionKey),
});

Step 5: Confirm Shares Stored

await client.updateSigningSharePairs({
  body: {
    signingSharePairIds: [newShares.SECP256K1.id, newShares.ED25519.id],
    status: "STORED_CLIENT",
  },
});

Full Recovery Flow

// 1. Get backup ciphertext
const secpCipherText = (await client.getBackupShareCipherText({
  path: { backupSharePairId: secp256k1BackupPairId },
})).cipherText;

const ed25519CipherText = (await client.getBackupShareCipherText({
  path: { backupSharePairId: ed25519BackupPairId },
})).cipherText;

// 2. Decrypt
const secpShare = decryptShare(secpCipherText,   userKey);
const ed25519Share = decryptShare(ed25519CipherText, userKey);

// 3. Recover
const newShares = await client.recoverWallet({
  body: {
    backupResponse: JSON.stringify({
      SECP256K1: { share: secpShare,    id: secp256k1BackupPairId },
      ED25519:   { share: ed25519Share, id: ed25519BackupPairId },
    }),
  },
});

// 4. Store new signing shares
await db.updateShares(userId, {
  secp256k1Share: encryptShare(newShares.SECP256K1.share, encryptionKey),
  ed25519Share:   encryptShare(newShares.ED25519.share,   encryptionKey),
});

// 5. Confirm
await client.updateSigningSharePairs({
  body: {
    signingSharePairIds: [newShares.SECP256K1.id, newShares.ED25519.id],
    status: "STORED_CLIENT",
  },
});