Skip to content

Commit 617fe68

Browse files
[SDK] feat: Allow custom storage for inApp and ecosystem wallets (#6432)
1 parent 01d0666 commit 617fe68

File tree

16 files changed

+67
-61
lines changed

16 files changed

+67
-61
lines changed

.changeset/gentle-books-begin.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Allow overriding storage for inApp and ecosystem wallets

packages/thirdweb/src/exports/storage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export {
1010
resolveArweaveScheme,
1111
type ResolveArweaveSchemeOptions,
1212
} from "../utils/arweave.js";
13+
export type { AsyncStorage } from "../utils/storage/AsyncStorage.js";

packages/thirdweb/src/react/web/ui/ConnectWallet/ConnectButton.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ export function ConnectButton(props: ConnectButtonProps) {
300300

301301
usePreloadWalletProviders({
302302
wallets,
303-
client: props.client,
304303
});
305304

306305
// Add props.chain and props.chains to defined chains store

packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/ConnectEmbed.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ export function ConnectEmbed(props: ConnectEmbedProps) {
215215

216216
usePreloadWalletProviders({
217217
wallets,
218-
client: props.client,
219218
});
220219

221220
const modalSize = useMemo(() => {

packages/thirdweb/src/react/web/utils/usePreloadWalletProviders.ts

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import { useQueries } from "@tanstack/react-query";
2-
import type { ThirdwebClient } from "../../../client/client.js";
32
import { COINBASE } from "../../../wallets/constants.js";
43
import { isEcosystemWallet } from "../../../wallets/ecosystem/is-ecosystem-wallet.js";
54
import type { Wallet } from "../../../wallets/interfaces/wallet.js";
65
import type { CreateWalletArgs } from "../../../wallets/wallet-types.js";
7-
import type { EcosystemWalletId } from "../../../wallets/wallet-types.js";
86

9-
export function usePreloadWalletProviders({
10-
client,
11-
wallets,
12-
}: { client: ThirdwebClient; wallets: Wallet[] }) {
7+
export function usePreloadWalletProviders({ wallets }: { wallets: Wallet[] }) {
138
useQueries({
149
queries: wallets
1510
.filter(
@@ -29,49 +24,6 @@ export function usePreloadWalletProviders({
2924
// return _something_
3025
return true;
3126
}
32-
case "inApp" === w.id: {
33-
const [
34-
{ InAppWebConnector },
35-
{ getOrCreateInAppWalletConnector },
36-
] = await Promise.all([
37-
import("../../../wallets/in-app/web/lib/web-connector.js"),
38-
import("../../../wallets/in-app/core/wallet/in-app-core.js"),
39-
]);
40-
await getOrCreateInAppWalletConnector(client, async (client) => {
41-
return new InAppWebConnector({
42-
client,
43-
});
44-
});
45-
// return _something_
46-
return true;
47-
}
48-
case isEcosystemWallet(w.id): {
49-
const [
50-
{ InAppWebConnector },
51-
{ getOrCreateInAppWalletConnector },
52-
] = await Promise.all([
53-
import("../../../wallets/in-app/web/lib/web-connector.js"),
54-
import("../../../wallets/in-app/core/wallet/in-app-core.js"),
55-
]);
56-
const ecosystemWallet = w as Wallet<EcosystemWalletId>; // we know this is an ecosystem wallet
57-
await getOrCreateInAppWalletConnector(
58-
client,
59-
async (client) => {
60-
return new InAppWebConnector({
61-
client,
62-
ecosystem: {
63-
id: ecosystemWallet.id,
64-
partnerId: ecosystemWallet.getConfig()?.partnerId,
65-
},
66-
});
67-
},
68-
{
69-
id: ecosystemWallet.id,
70-
partnerId: ecosystemWallet.getConfig()?.partnerId,
71-
},
72-
);
73-
return true;
74-
}
7527
// potentially add more wallets here
7628
default: {
7729
return false;

packages/thirdweb/src/wallets/ecosystem/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { SupportedSmsCountry } from "../../react/web/wallets/in-app/supported-sms-countries.js";
2+
import type { AsyncStorage } from "../../utils/storage/AsyncStorage.js";
23
import type {
34
InAppWalletAutoConnectOptions,
45
InAppWalletConnectionOptions,
@@ -23,6 +24,10 @@ export type EcosystemWalletCreationOptions = {
2324
* The partnerId of the ecosystem wallet to connect to
2425
*/
2526
partnerId?: string;
27+
/**
28+
* The storage to use for storing wallet state
29+
*/
30+
storage?: AsyncStorage;
2631
};
2732

2833
export type EcosystemWalletConnectionOptions = InAppWalletConnectionOptions;

packages/thirdweb/src/wallets/in-app/core/wallet/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Chain } from "../../../../chains/types.js";
22
import type { ThirdwebClient } from "../../../../client/client.js";
33
import type { SupportedSmsCountry } from "../../../../react/web/wallets/in-app/supported-sms-countries.js";
4+
import type { AsyncStorage } from "../../../../utils/storage/AsyncStorage.js";
45
import type { Prettify } from "../../../../utils/type-utils.js";
56
import type { SmartWalletOptions } from "../../../smart/types.js";
67
import type {
@@ -87,5 +88,9 @@ export type InAppWalletCreationOptions =
8788
* Whether to hide the private key export button in the Connect Modal
8889
*/
8990
hidePrivateKeyExport?: boolean;
91+
/**
92+
* The storage to use for storing wallet state
93+
*/
94+
storage?: AsyncStorage;
9095
}
9196
| undefined;

packages/thirdweb/src/wallets/in-app/native/auth/passkeys.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { concat } from "viem";
33
import type { ThirdwebClient } from "../../../../client/client.js";
44
import { toBytes } from "../../../../utils/encoding/to-bytes.js";
55
import { keccak256 } from "../../../../utils/hashing/keccak256.js";
6+
import type { AsyncStorage } from "../../../../utils/storage/AsyncStorage.js";
67
import { nativeLocalStorage } from "../../../../utils/storage/nativeStorage.js";
78
import {
89
base64ToString,
@@ -121,13 +122,14 @@ export class PasskeyNativeClient implements PasskeyClient {
121122
export async function hasStoredPasskey(
122123
client: ThirdwebClient,
123124
ecosystemId?: EcosystemWalletId,
125+
storage?: AsyncStorage,
124126
) {
125-
const storage = new ClientScopedStorage({
126-
storage: nativeLocalStorage,
127+
const clientStorage = new ClientScopedStorage({
128+
storage: storage ?? nativeLocalStorage,
127129
clientId: client.clientId,
128130
ecosystem: ecosystemId ? { id: ecosystemId } : undefined,
129131
});
130-
const credId = await storage.getPasskeyCredentialId();
132+
const credId = await clientStorage.getPasskeyCredentialId();
131133
return !!credId;
132134
}
133135

packages/thirdweb/src/wallets/in-app/native/ecosystem.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export function ecosystemWallet(
8383
return new InAppNativeConnector({
8484
client,
8585
ecosystem,
86+
storage: createOptions?.storage,
8687
// TODO (enclave): passkeyDomain for ecosystem wallets
8788
});
8889
},

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export function inAppWallet(
6565
return new InAppNativeConnector({
6666
client,
6767
passkeyDomain: createOptions?.auth?.passkeyDomain,
68+
storage: createOptions?.storage,
6869
});
6970
},
7071
}) as Wallet<"inApp">;

packages/thirdweb/src/wallets/in-app/native/native-connector.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ThirdwebClient } from "../../../client/client.js";
22
import { stringify } from "../../../utils/json.js";
3+
import type { AsyncStorage } from "../../../utils/storage/AsyncStorage.js";
34
import { nativeLocalStorage } from "../../../utils/storage/nativeStorage.js";
45
import type { Account } from "../../interfaces/wallet.js";
56
import { getUserStatus } from "../core/actions/get-enclave-user-status.js";
@@ -37,11 +38,11 @@ import { sendOtp, verifyOtp } from "../web/lib/auth/otp.js";
3738
import { deleteActiveAccount, socialAuth } from "./auth/native-auth.js";
3839
import { logoutUser } from "./helpers/auth/logout.js";
3940
import { ShardedWallet } from "./helpers/wallet/sharded-wallet.js";
40-
4141
type NativeConnectorOptions = {
4242
client: ThirdwebClient;
4343
ecosystem?: Ecosystem;
4444
passkeyDomain?: string;
45+
storage?: AsyncStorage;
4546
};
4647

4748
export class InAppNativeConnector implements InAppConnector {
@@ -56,7 +57,7 @@ export class InAppNativeConnector implements InAppConnector {
5657
this.passkeyDomain = options.passkeyDomain;
5758
this.ecosystem = options.ecosystem;
5859
this.storage = new ClientScopedStorage({
59-
storage: nativeLocalStorage,
60+
storage: options.storage ?? nativeLocalStorage,
6061
clientId: this.client.clientId,
6162
ecosystem: options.ecosystem,
6263
});

packages/thirdweb/src/wallets/in-app/web/ecosystem.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export function ecosystemWallet(
7373
return new InAppWebConnector({
7474
client,
7575
ecosystem,
76+
storage: createOptions?.storage,
7677
});
7778
},
7879
}) as Wallet<EcosystemWalletId>;

packages/thirdweb/src/wallets/in-app/web/in-app.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,31 @@ import { createInAppWallet } from "../core/wallet/in-app-core.js";
222222
* });
223223
* ```
224224
*
225+
* ### Override storage for the wallet state
226+
*
227+
* By default, wallet state is stored in the browser's local storage. You can override this behavior by providing a custom storage object, useful for server side integrations.
228+
*
229+
* ```ts
230+
* import { inAppWallet } from "thirdweb/wallets";
231+
* import { AsyncStorage } from "thirdweb/storage";
232+
*
233+
* const myStorage: AsyncStorage = {
234+
* getItem: async (key) => {
235+
* return customGet(`CUSTOM_STORAGE_KEY${key}`);
236+
* },
237+
* setItem: async (key, value) => {
238+
* return customSet(`CUSTOM_STORAGE_KEY${key}`, value);
239+
* },
240+
* removeItem: async (key) => {
241+
* return customRemove(`CUSTOM_STORAGE_KEY${key}`);
242+
* },
243+
* };
244+
*
245+
* const wallet = inAppWallet({
246+
* storage: myStorage,
247+
* });
248+
* ```
249+
*
225250
* @returns The created in-app wallet.
226251
* @wallet
227252
*/
@@ -235,6 +260,7 @@ export function inAppWallet(
235260
return new InAppWebConnector({
236261
client,
237262
passkeyDomain: createOptions?.auth?.passkeyDomain,
263+
storage: createOptions?.storage,
238264
});
239265
},
240266
}) as Wallet<"inApp">;

packages/thirdweb/src/wallets/in-app/web/lib/auth/passkeys.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { client, parsers } from "@passwordless-id/webauthn";
22
import type { ThirdwebClient } from "../../../../../client/client.js";
3+
import type { AsyncStorage } from "../../../../../utils/storage/AsyncStorage.js";
34
import { webLocalStorage } from "../../../../../utils/storage/webStorage.js";
45
import {
56
base64ToString,
@@ -83,12 +84,13 @@ export class PasskeyWebClient implements PasskeyClient {
8384
export async function hasStoredPasskey(
8485
client: ThirdwebClient,
8586
ecosystemId?: EcosystemWalletId,
87+
storage?: AsyncStorage,
8688
) {
87-
const storage = new ClientScopedStorage({
88-
storage: webLocalStorage, // TODO (passkey) react native variant of this fn
89+
const clientStorage = new ClientScopedStorage({
90+
storage: storage ?? webLocalStorage, // TODO (passkey) react native variant of this fn
8991
clientId: client.clientId,
9092
ecosystem: ecosystemId ? { id: ecosystemId } : undefined,
9193
});
92-
const credId = await storage.getPasskeyCredentialId();
94+
const credId = await clientStorage.getPasskeyCredentialId();
9395
return !!credId;
9496
}

packages/thirdweb/src/wallets/in-app/web/lib/web-connector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export class InAppWebConnector implements InAppConnector {
7474
onAuthSuccess,
7575
ecosystem,
7676
passkeyDomain,
77+
storage,
7778
}: InAppWalletConstructorType) {
7879
if (this.isClientIdLegacyPaper(client.clientId)) {
7980
throw new Error(
@@ -85,7 +86,7 @@ export class InAppWebConnector implements InAppConnector {
8586
this.ecosystem = ecosystem;
8687
this.passkeyDomain = passkeyDomain;
8788
this.storage = new ClientScopedStorage({
88-
storage: webLocalStorage,
89+
storage: storage ?? webLocalStorage,
8990
clientId: client.clientId,
9091
ecosystem: ecosystem,
9192
});

packages/thirdweb/src/wallets/in-app/web/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
// types for class constructors still a little messy right now.
33

44
import type { ThirdwebClient } from "../../../client/client.js";
5+
import type { AsyncStorage } from "../../../utils/storage/AsyncStorage.js";
56
import type { AuthAndWalletRpcReturnType } from "../core/authentication/types.js";
67
import type { Ecosystem } from "../core/wallet/types.js";
78
import type { InAppWalletIframeCommunicator } from "./utils/iFrameCommunication/InAppWalletIframeCommunicator.js";
89

9-
// Open to PRs from whoever sees this and knows of a cleaner way to handle things
1010
type ClientIdConstructorType = {
1111
/**
1212
* the clientId of your API Key. You can create an API key by creating a project on thirdweb dashboard.
@@ -29,6 +29,11 @@ export type InAppWalletConstructorType = ClientIdConstructorType & {
2929
* The domain of the passkey to use for authentication
3030
*/
3131
passkeyDomain?: string;
32+
33+
/**
34+
* The storage to use for storing wallet state
35+
*/
36+
storage?: AsyncStorage;
3237
};
3338

3439
export type ClientIdWithQuerierType = ClientIdConstructorType & {

0 commit comments

Comments
 (0)