diff --git a/packages/thirdweb/src/wallets/in-app/native/helpers/constants.ts b/packages/thirdweb/src/wallets/in-app/native/helpers/constants.ts index 2165dacb991..cf67abece5f 100644 --- a/packages/thirdweb/src/wallets/in-app/native/helpers/constants.ts +++ b/packages/thirdweb/src/wallets/in-app/native/helpers/constants.ts @@ -6,6 +6,8 @@ export const AUTH_SHARE_INDEX = AUTH_SHARE_ID - 1; const DEVICE_SHARE_ID = 1; export const DEVICE_SHARE_INDEX = DEVICE_SHARE_ID - 1; export const DEVICE_SHARE_MISSING_MESSAGE = "Missing device share."; +export const INVALID_DEVICE_SHARE_MESSAGE = + "Invalid private key reconstructed from shares"; const RECOVERY_SHARE_ID = 2; export const RECOVERY_SHARE_INDEX = RECOVERY_SHARE_ID - 1; diff --git a/packages/thirdweb/src/wallets/in-app/native/helpers/storage/local.ts b/packages/thirdweb/src/wallets/in-app/native/helpers/storage/local.ts index 02fa8a2e37c..2784673c104 100644 --- a/packages/thirdweb/src/wallets/in-app/native/helpers/storage/local.ts +++ b/packages/thirdweb/src/wallets/in-app/native/helpers/storage/local.ts @@ -112,6 +112,22 @@ export async function setDeviceShare({ return deviceShare; } +export async function removeDeviceShare({ + clientId, +}: { + clientId: string; +}): Promise { + const userDetails = await getWalletUserDetails(clientId); + + if (!userDetails) { + throw new Error("Missing wallet user ID"); + } + + const name = DEVICE_SHARE_LOCAL_STORAGE_NAME(clientId, userDetails.userId); + await removeItemInAsyncStorage(name); + return; +} + export async function getDeviceShare(clientId: string) { const cachedWalletUserId = await getWalletUserDetails(clientId); if (!cachedWalletUserId) { diff --git a/packages/thirdweb/src/wallets/in-app/native/helpers/wallet/retrieval.ts b/packages/thirdweb/src/wallets/in-app/native/helpers/wallet/retrieval.ts index 4f986169c65..f87861c2459 100644 --- a/packages/thirdweb/src/wallets/in-app/native/helpers/wallet/retrieval.ts +++ b/packages/thirdweb/src/wallets/in-app/native/helpers/wallet/retrieval.ts @@ -11,9 +11,10 @@ import type { SetUpWalletRpcReturnType } from "../../../core/authentication/type import { getUserShares } from "../api/fetchers.js"; import { DEVICE_SHARE_MISSING_MESSAGE, + INVALID_DEVICE_SHARE_MESSAGE, ROUTE_GET_USER_SHARES, } from "../constants.js"; -import { getDeviceShare } from "../storage/local.js"; +import { getDeviceShare, removeDeviceShare } from "../storage/local.js"; import { storeShares } from "./creation.js"; import { decryptShareWeb } from "./encryption.js"; @@ -47,7 +48,7 @@ async function getWalletPrivateKeyFromShares(shares: string[]) { } const prefixPrivateKey = hexToString(privateKeyHex as Hex); if (!prefixPrivateKey.startsWith("thirdweb_")) { - throw new Error("Invalid private key reconstructed from shares"); + throw new Error(INVALID_DEVICE_SHARE_MESSAGE); } const privateKey = prefixPrivateKey.replace("thirdweb_", ""); return privateKey; @@ -58,9 +59,24 @@ async function getAccountFromShares(args: { shares: string[]; }): Promise { const { client, shares } = args; + const privateKey = await (async () => { + try { + return await getWalletPrivateKeyFromShares(shares); + } catch (e) { + // If the private key reconstruction fails, try to reset the device share and prompt the user to try again + // This can happen if a user's account has been migrated or otherwise modified in the backend to use a new wallet. In that case, we need to reset their device state to get a new share + if (e instanceof Error && e.message === INVALID_DEVICE_SHARE_MESSAGE) { + await removeDeviceShare({ clientId: client.clientId }); + throw new Error("Invalid device state, please try again."); + } + // Otherwise this is a legitimate error, throw it + throw e; + } + })(); + return privateKeyToAccount({ client, - privateKey: await getWalletPrivateKeyFromShares(shares), + privateKey, }); }