Skip to content

Commit ec0f8d2

Browse files
authored
[React Native SDK] Fix: Remove device share if recovering private key fails (#4976)
1 parent ccd4cd6 commit ec0f8d2

File tree

3 files changed

+37
-3
lines changed

3 files changed

+37
-3
lines changed

packages/thirdweb/src/wallets/in-app/native/helpers/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export const AUTH_SHARE_INDEX = AUTH_SHARE_ID - 1;
66
const DEVICE_SHARE_ID = 1;
77
export const DEVICE_SHARE_INDEX = DEVICE_SHARE_ID - 1;
88
export const DEVICE_SHARE_MISSING_MESSAGE = "Missing device share.";
9+
export const INVALID_DEVICE_SHARE_MESSAGE =
10+
"Invalid private key reconstructed from shares";
911

1012
const RECOVERY_SHARE_ID = 2;
1113
export const RECOVERY_SHARE_INDEX = RECOVERY_SHARE_ID - 1;

packages/thirdweb/src/wallets/in-app/native/helpers/storage/local.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,22 @@ export async function setDeviceShare({
112112
return deviceShare;
113113
}
114114

115+
export async function removeDeviceShare({
116+
clientId,
117+
}: {
118+
clientId: string;
119+
}): Promise<void> {
120+
const userDetails = await getWalletUserDetails(clientId);
121+
122+
if (!userDetails) {
123+
throw new Error("Missing wallet user ID");
124+
}
125+
126+
const name = DEVICE_SHARE_LOCAL_STORAGE_NAME(clientId, userDetails.userId);
127+
await removeItemInAsyncStorage(name);
128+
return;
129+
}
130+
115131
export async function getDeviceShare(clientId: string) {
116132
const cachedWalletUserId = await getWalletUserDetails(clientId);
117133
if (!cachedWalletUserId) {

packages/thirdweb/src/wallets/in-app/native/helpers/wallet/retrieval.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import type { SetUpWalletRpcReturnType } from "../../../core/authentication/type
1111
import { getUserShares } from "../api/fetchers.js";
1212
import {
1313
DEVICE_SHARE_MISSING_MESSAGE,
14+
INVALID_DEVICE_SHARE_MESSAGE,
1415
ROUTE_GET_USER_SHARES,
1516
} from "../constants.js";
16-
import { getDeviceShare } from "../storage/local.js";
17+
import { getDeviceShare, removeDeviceShare } from "../storage/local.js";
1718
import { storeShares } from "./creation.js";
1819
import { decryptShareWeb } from "./encryption.js";
1920

@@ -47,7 +48,7 @@ async function getWalletPrivateKeyFromShares(shares: string[]) {
4748
}
4849
const prefixPrivateKey = hexToString(privateKeyHex as Hex);
4950
if (!prefixPrivateKey.startsWith("thirdweb_")) {
50-
throw new Error("Invalid private key reconstructed from shares");
51+
throw new Error(INVALID_DEVICE_SHARE_MESSAGE);
5152
}
5253
const privateKey = prefixPrivateKey.replace("thirdweb_", "");
5354
return privateKey;
@@ -58,9 +59,24 @@ async function getAccountFromShares(args: {
5859
shares: string[];
5960
}): Promise<Account> {
6061
const { client, shares } = args;
62+
const privateKey = await (async () => {
63+
try {
64+
return await getWalletPrivateKeyFromShares(shares);
65+
} catch (e) {
66+
// If the private key reconstruction fails, try to reset the device share and prompt the user to try again
67+
// 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
68+
if (e instanceof Error && e.message === INVALID_DEVICE_SHARE_MESSAGE) {
69+
await removeDeviceShare({ clientId: client.clientId });
70+
throw new Error("Invalid device state, please try again.");
71+
}
72+
// Otherwise this is a legitimate error, throw it
73+
throw e;
74+
}
75+
})();
76+
6177
return privateKeyToAccount({
6278
client,
63-
privateKey: await getWalletPrivateKeyFromShares(shares),
79+
privateKey,
6480
});
6581
}
6682

0 commit comments

Comments
 (0)