Skip to content

Commit d89f009

Browse files
authored
feat: Wallet connection improvements (#2632)
1 parent 1c29556 commit d89f009

File tree

7 files changed

+108
-77
lines changed

7 files changed

+108
-77
lines changed

.changeset/little-falcons-remain.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Various Improvements for wallet connection
6+
7+
- change `accountsChanged` event to `accountChanged` event and emit new `Account` object instead of creating it in the connection manager
8+
- WalletConnect connection improvements

packages/thirdweb/src/wallets/coinbase/coinbaseSDKWallet.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,14 @@ function onConnect(
229229
}
230230

231231
function onAccountsChanged(accounts: string[]) {
232-
if (accounts.length === 0) {
233-
onDisconnect();
232+
if (accounts[0]) {
233+
const newAccount = {
234+
...account,
235+
address: getAddress(accounts[0]),
236+
};
237+
emitter.emit("accountChanged", newAccount);
234238
} else {
235-
emitter.emit("accountsChanged", accounts);
239+
onDisconnect();
236240
}
237241
}
238242

packages/thirdweb/src/wallets/create-wallet.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ export function createWallet<const ID extends WalletId>(
8484
unsubscribeDisconnect();
8585
});
8686

87+
emitter.subscribe("accountChanged", (_account) => {
88+
account = _account;
89+
});
90+
8791
let handleSwitchChain: (chain: Chain) => Promise<void> = async () => {
8892
throw new Error("Not implemented yet");
8993
};
@@ -463,6 +467,10 @@ function coinbaseWalletSDK(): Wallet<"com.coinbase.wallet"> {
463467
unsubscribeDisconnect();
464468
});
465469

470+
emitter.subscribe("accountChanged", (_account) => {
471+
account = _account;
472+
});
473+
466474
return {
467475
id: "com.coinbase.wallet",
468476
subscribe: emitter.subscribe,

packages/thirdweb/src/wallets/injected/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,15 @@ async function onConnect(
200200
}
201201

202202
function onAccountsChanged(accounts: string[]) {
203-
if (accounts.length === 0) {
204-
onDisconnect();
203+
if (accounts[0]) {
204+
const newAccount = {
205+
...account,
206+
address: getAddress(accounts[0]),
207+
};
208+
209+
emitter.emit("accountChanged", newAccount);
205210
} else {
206-
emitter.emit("accountsChanged", accounts);
211+
onDisconnect();
207212
}
208213
}
209214

packages/thirdweb/src/wallets/manager/index.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,12 @@ export function createConnectionManager(storage: AsyncStorage) {
102102

103103
// setup listeners
104104

105-
const onAccountsChanged = (addresses: string[]) => {
106-
const newAddress = addresses[0];
107-
if (!newAddress) {
108-
onWalletDisconnect(wallet);
109-
return;
110-
} else {
111-
// TODO: get this new object as argument from onAccountsChanged
112-
// this requires emitting events from the wallet
113-
const newAccount: Account = {
114-
...account,
115-
address: newAddress,
116-
};
117-
118-
activeAccountStore.setValue(newAccount);
119-
}
105+
const onAccountsChanged = (newAccount: Account) => {
106+
activeAccountStore.setValue(newAccount);
120107
};
121108

122-
const unsubAccounts = wallet.subscribe(
123-
"accountsChanged",
124-
onAccountsChanged,
125-
);
109+
const unsubAccounts = wallet.subscribe("accountChanged", onAccountsChanged);
110+
126111
const unsubChainChanged = wallet.subscribe("chainChanged", (chain) =>
127112
activeWalletChainStore.setValue(chain),
128113
);

packages/thirdweb/src/wallets/wallet-connect/index.ts

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
getRpcUrlForChain,
2020
} from "../../chains/utils.js";
2121
import type { Chain } from "../../chains/types.js";
22-
import { ethereum } from "../../chains/chain-definitions/ethereum.js";
2322
import { isHex, numberToHex, type Hex } from "../../utils/encoding/hex.js";
2423
import { getDefaultAppMetadata } from "../utils/defaultDappMetadata.js";
2524
import type { WCSupportedWalletIds } from "../__generated__/wallet-ids.js";
@@ -34,6 +33,8 @@ import {
3433
getSavedConnectParamsFromStorage,
3534
saveConnectParamsToStorage,
3635
} from "../storage/walletStorage.js";
36+
import type { ThirdwebClient } from "../../client/client.js";
37+
import { getAddress } from "../../utils/address.js";
3738

3839
const asyncLocalStorage =
3940
typeof window !== "undefined" ? _asyncLocalStorage : undefined;
@@ -42,7 +43,7 @@ type WCProvider = InstanceType<typeof EthereumProvider>;
4243

4344
type SavedConnectParams = {
4445
optionalChains?: Chain[];
45-
chain: Chain;
46+
chain?: Chain;
4647
pairingTopic?: string;
4748
};
4849

@@ -76,14 +77,6 @@ export async function connectWC(
7677

7778
const wcOptions = options.walletConnect;
7879

79-
const targetChain = options?.chain || ethereum;
80-
const targetChainId = targetChain.id;
81-
82-
const rpc = getRpcUrlForChain({
83-
chain: targetChain,
84-
client: options.client,
85-
});
86-
8780
const { onDisplayUri } = wcOptions || {};
8881

8982
if (onDisplayUri) {
@@ -92,17 +85,23 @@ export async function connectWC(
9285
}
9386
}
9487

88+
const { rpcMap, chainsToRequest } = getChainsToRequest({
89+
client: options.client,
90+
chain: options.chain,
91+
optionalChains: options.walletConnect?.optionalChains,
92+
});
93+
9594
// If there no active session, or the chain is stale, force connect.
9695
if (!provider.session || _isChainsState) {
9796
await provider.connect({
98-
pairingTopic: wcOptions?.pairingTopic,
99-
chains: [Number(targetChainId)],
100-
rpcMap: {
101-
[targetChainId.toString()]: rpc,
102-
},
97+
...(wcOptions?.pairingTopic
98+
? { pairingTopic: wcOptions?.pairingTopic }
99+
: {}),
100+
optionalChains: chainsToRequest,
101+
rpcMap: rpcMap,
103102
});
104103

105-
setRequestedChainsIds([targetChainId]);
104+
setRequestedChainsIds(chainsToRequest);
106105
}
107106

108107
// If session exists and chains are authorized, enable provider for required chain
@@ -117,7 +116,7 @@ export async function connectWC(
117116
if (options) {
118117
const savedParams: SavedConnectParams = {
119118
optionalChains: options.walletConnect?.optionalChains,
120-
chain: chain,
119+
chain: options.chain,
121120
pairingTopic: options.walletConnect?.pairingTopic,
122121
};
123122

@@ -205,11 +204,10 @@ async function initProvider(
205204
"@walletconnect/ethereum-provider"
206205
);
207206

208-
const targetChain = options.chain || ethereum;
209-
210-
const rpc = getRpcUrlForChain({
211-
chain: targetChain,
207+
const { rpcMap, chainsToRequest } = getChainsToRequest({
212208
client: options.client,
209+
chain: options.chain,
210+
optionalChains: options.walletConnect?.optionalChains,
213211
});
214212

215213
const provider = await EthereumProvider.init({
@@ -220,7 +218,7 @@ async function initProvider(
220218
projectId: wcOptions?.projectId || defaultWCProjectId,
221219
optionalMethods: OPTIONAL_METHODS,
222220
optionalEvents: OPTIONAL_EVENTS,
223-
optionalChains: [targetChain.id],
221+
optionalChains: chainsToRequest,
224222
metadata: {
225223
name: wcOptions?.appMetadata?.name || getDefaultAppMetadata().name,
226224
description:
@@ -231,9 +229,7 @@ async function initProvider(
231229
wcOptions?.appMetadata?.logoUrl || getDefaultAppMetadata().logoUrl,
232230
],
233231
},
234-
rpcMap: {
235-
[targetChain.id]: rpc,
236-
},
232+
rpcMap: rpcMap,
237233
qrModalOptions: wcOptions?.qrModalOptions,
238234
disableProviderPing: true,
239235
});
@@ -242,15 +238,7 @@ async function initProvider(
242238

243239
// disconnect the provider if chains are stale when (if not auto connecting)
244240
if (!isAutoConnect) {
245-
const chains = [
246-
targetChain,
247-
...(options?.walletConnect?.optionalChains || []),
248-
];
249-
250-
const isStale = await isChainsStale(
251-
provider,
252-
chains.map((c) => c.id),
253-
);
241+
const isStale = await isChainsStale(provider, chainsToRequest);
254242

255243
if (isStale && provider.session) {
256244
await provider.disconnect();
@@ -293,20 +281,6 @@ async function initProvider(
293281
});
294282
}
295283

296-
// try switching to correct chain
297-
if (options?.chain && provider.chainId !== options?.chain.id) {
298-
try {
299-
await switchChainWC(provider, options.chain);
300-
} catch (e) {
301-
console.error("Failed to Switch chain to target chain");
302-
console.error(e);
303-
// throw only if not auto connecting
304-
if (!isAutoConnect) {
305-
throw e;
306-
}
307-
}
308-
}
309-
310284
return provider;
311285
}
312286

@@ -385,10 +359,14 @@ function onConnect(
385359
}
386360

387361
function onAccountsChanged(accounts: string[]) {
388-
if (accounts.length === 0) {
389-
onDisconnect();
362+
if (accounts[0]) {
363+
const newAccount: Account = {
364+
...account,
365+
address: getAddress(accounts[0]),
366+
};
367+
emitter.emit("accountChanged", newAccount);
390368
} else {
391-
emitter.emit("accountsChanged", accounts);
369+
onDisconnect();
392370
}
393371
}
394372

@@ -521,3 +499,45 @@ async function getRequestedChainsIds(): Promise<number[]> {
521499
const data = localStorage.getItem(storageKeys.requestedChains);
522500
return data ? JSON.parse(data) : [];
523501
}
502+
503+
type ArrayOneOrMore<T> = {
504+
0: T;
505+
} & Array<T>;
506+
507+
function getChainsToRequest(options: {
508+
chain?: Chain;
509+
optionalChains?: Chain[];
510+
client: ThirdwebClient;
511+
}) {
512+
const rpcMap: Record<number, string> = {};
513+
514+
if (options.chain) {
515+
rpcMap[options.chain.id] = getRpcUrlForChain({
516+
chain: options.chain,
517+
client: options.client,
518+
});
519+
}
520+
521+
// limit optional chains to 10
522+
const optionalChains = (options?.optionalChains || []).slice(0, 10);
523+
524+
optionalChains.forEach((chain) => {
525+
rpcMap[chain.id] = getRpcUrlForChain({
526+
chain: chain,
527+
client: options.client,
528+
});
529+
});
530+
531+
const optionalChainIds = optionalChains.map((c) => c.id) || [];
532+
533+
const chainsToRequest: ArrayOneOrMore<number> = options.chain
534+
? [options.chain.id, ...optionalChainIds]
535+
: optionalChainIds.length > 0
536+
? (optionalChainIds as ArrayOneOrMore<number>)
537+
: [1];
538+
539+
return {
540+
rpcMap,
541+
chainsToRequest,
542+
};
543+
}

packages/thirdweb/src/wallets/wallet-emitter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { Chain } from "../chains/types.js";
22
import { createEmitter, type Emitter } from "../utils/tiny-emitter.js";
3+
import type { Account } from "./interfaces/wallet.js";
34
import type { WalletAutoConnectionOption, WalletId } from "./wallet-types.js";
45

56
export type WalletEmitterEvents<TWalletId extends WalletId> = {
6-
accountsChanged: string[];
7+
accountChanged: Account;
78
disconnect?: never;
89
chainChanged: Chain;
910
onConnect: WalletAutoConnectionOption<TWalletId>;

0 commit comments

Comments
 (0)