Backup Wallet
Use backupWallet() to create encrypted recovery shares, securely store backup data, and enable reliable wallet recovery for MPC wallets.
A backup lets your users recover their wallet if the client share is lost. The backup process produces a second set of shares (one per curve) using a different MPC round — Tatum's backup share is different from its signing share, so backups don't weaken the signing security model.
How Backups Work
backupWallet()— the enclave produces a backup share pair using the original generate response- You encrypt the client backup share and store it (in Portal, your own backend, or the user's device)
updateBackupSharePairs()— you confirm the backup shares are stored
During recovery, both the encrypted client backup share and Tatum's custodian backup share are required. Neither alone can reconstruct the signing shares.
Step 1: Generate Backup Shares
Pass the original generateWallet() response (JSON-stringified) to backupWallet():
// shares = result of generateWallet()
const backup = await client.backupWallet({
body: {
generateResponse: JSON.stringify(shares),
},
});
// backup.SECP256K1 → { share: string, id: string }
// backup.ED25519 → { share: string, id: string }The backup.*.id values are the backupSharePairIds used in subsequent steps.
Step 2: Encrypt Backup Shares
Encrypt each backup share with a key only the user controls (passphrase, passkey, KMS, etc.):
import { createCipheriv, randomBytes, createDecipheriv } from "crypto";
function encryptShare(share: string, keyHex: string): string {
const key = Buffer.from(keyHex, "hex");
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const ciphertext = Buffer.concat([cipher.update(share, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, tag, ciphertext]).toString("base64");
}
const userKey = deriveKeyFromPassphrase(userPassphrase); // your implementation
const encryptedSecp = encryptShare(backup.SECP256K1.share, userKey);
const encryptedEd = encryptShare(backup.ED25519.share, userKey);Step 3: Store Encrypted Backup Shares
Option A: Store with Portal (Portal-Managed Backup)
await client.storeEncryptedBackupShare({
path: { backupSharePairId: backup.SECP256K1.id },
body: { clientCipherText: encryptedSecp },
});
await client.storeEncryptedBackupShare({
path: { backupSharePairId: backup.ED25519.id },
body: { clientCipherText: encryptedEd },
});Option B: Store in Your Own Backend
Store the ciphertext in your database alongside the backupSharePairId. Skip the storeEncryptedBackupShare() call — you'll retrieve it yourself during recovery.
Step 4: Mark Backup Complete
await client.updateBackupSharePairs({
body: {
backupSharePairIds: [backup.SECP256K1.id, backup.ED25519.id],
status: "STORED_CLIENT_BACKUP_SHARE",
},
});Use "STORED_CLIENT_BACKUP_SHARE_KEY" if you're using a passkey or hardware key method.
Full Backup Flow
// 1. Generate backup shares
const backup = await client.backupWallet({
body: { generateResponse: JSON.stringify(shares) },
});
// 2. Encrypt
const encrypted = {
SECP256K1: encryptShare(backup.SECP256K1.share, userKey),
ED25519: encryptShare(backup.ED25519.share, userKey),
};
// 3. Store with Portal
for (const curve of ["SECP256K1", "ED25519"] as const) {
await client.storeEncryptedBackupShare({
path: { backupSharePairId: backup[curve].id },
body: { clientCipherText: encrypted[curve] },
});
}
// 4. Confirm stored
await client.updateBackupSharePairs({
body: {
backupSharePairIds: [backup.SECP256K1.id, backup.ED25519.id],
status: "STORED_CLIENT_BACKUP_SHARE",
},
});