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 stringSee 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 shareThe 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",
},
});