@tomo-inc/wallet-adaptor-base 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,302 @@
1
+ import { ProviderStandard } from "@tomo-inc/wallet-utils";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { walletStandardWallets } from "../wallets/wallet-standard";
4
+
5
+ // Mock @wallet-standard/core - use vi.hoisted to avoid initialization issues
6
+ const { mockWallets, mockWalletsAPI } = vi.hoisted(() => {
7
+ const wallets: any[] = [];
8
+ const walletsAPI = {
9
+ get: () => wallets,
10
+ };
11
+ return {
12
+ mockWallets: wallets,
13
+ mockWalletsAPI: walletsAPI,
14
+ };
15
+ });
16
+
17
+ vi.mock("@wallet-standard/core", () => ({
18
+ getWallets: () => mockWalletsAPI,
19
+ }));
20
+
21
+ describe("wallet-standard", () => {
22
+ beforeEach(() => {
23
+ vi.clearAllMocks();
24
+ mockWallets.length = 0;
25
+ vi.useFakeTimers();
26
+ });
27
+
28
+ afterEach(() => {
29
+ vi.restoreAllMocks();
30
+ vi.useRealTimers();
31
+ });
32
+
33
+ describe("walletStandardWallets", () => {
34
+ it("should return empty arrays when no wallets found", async () => {
35
+ const promise = walletStandardWallets();
36
+ vi.advanceTimersByTime(500);
37
+ const result = await promise;
38
+
39
+ expect(result.solanaWallets).toEqual([]);
40
+ expect(result.aptosWallets).toEqual([]);
41
+ expect(result.suiWallets).toEqual([]);
42
+ });
43
+
44
+ it("should detect Solana wallets", async () => {
45
+ mockWallets.push({
46
+ name: "Solana Wallet",
47
+ icon: "solana-icon.png",
48
+ rdns: "com.solana.wallet",
49
+ homepage: "https://solana.wallet",
50
+ chains: ["solana:mainnet"],
51
+ features: {
52
+ "solana:signTransaction": {},
53
+ "solana:signMessage": {},
54
+ "standard:connect": {
55
+ connect: vi.fn(),
56
+ },
57
+ },
58
+ });
59
+
60
+ const promise = walletStandardWallets();
61
+ vi.advanceTimersByTime(500);
62
+ const result = await promise;
63
+
64
+ expect(result.solanaWallets).toHaveLength(1);
65
+ expect(result.solanaWallets[0].info.name).toBe("Solana Wallet");
66
+ expect(result.solanaWallets[0].connectors.solana?.protocol).toBe(ProviderStandard.WALLET_STANDARD);
67
+ expect(result.solanaWallets[0].isInstalled).toBe(true);
68
+ });
69
+
70
+ it("should detect Aptos wallets", async () => {
71
+ mockWallets.push({
72
+ name: "Aptos Wallet",
73
+ icon: "aptos-icon.png",
74
+ rdns: "com.aptos.wallet",
75
+ homepage: "https://aptos.wallet",
76
+ chains: ["aptos:mainnet"],
77
+ features: {
78
+ "aptos:signTransaction": {},
79
+ "aptos:signMessage": {},
80
+ },
81
+ });
82
+
83
+ const promise = walletStandardWallets();
84
+ vi.advanceTimersByTime(500);
85
+ const result = await promise;
86
+
87
+ expect(result.aptosWallets).toHaveLength(1);
88
+ expect(result.aptosWallets[0].info.name).toBe("Aptos Wallet");
89
+ expect(result.aptosWallets[0].connectors.aptos?.protocol).toBe(ProviderStandard.WALLET_STANDARD);
90
+ });
91
+
92
+ it("should detect Sui wallets", async () => {
93
+ mockWallets.push({
94
+ name: "Sui Wallet",
95
+ icon: "sui-icon.png",
96
+ rdns: "com.sui.wallet",
97
+ homepage: "https://sui.wallet",
98
+ chains: ["sui:mainnet"],
99
+ features: {
100
+ "sui:signTransaction": {},
101
+ "sui:signMessage": {},
102
+ },
103
+ });
104
+
105
+ const promise = walletStandardWallets();
106
+ vi.advanceTimersByTime(500);
107
+ const result = await promise;
108
+
109
+ expect(result.suiWallets).toHaveLength(1);
110
+ expect(result.suiWallets[0].info.name).toBe("Sui Wallet");
111
+ });
112
+
113
+ it("should filter out disabled wallets", async () => {
114
+ mockWallets.push({
115
+ name: "Bitget Wallet",
116
+ icon: "bitget-icon.png",
117
+ chains: ["solana:mainnet"],
118
+ features: {
119
+ "solana:signTransaction": {},
120
+ "standard:connect": {},
121
+ },
122
+ });
123
+
124
+ const promise = walletStandardWallets();
125
+ vi.advanceTimersByTime(500);
126
+ const result = await promise;
127
+
128
+ expect(result.solanaWallets).toHaveLength(0);
129
+ });
130
+
131
+ it("should rename Trust wallet", async () => {
132
+ mockWallets.push({
133
+ name: "trust",
134
+ icon: "trust-icon.png",
135
+ chains: ["solana:mainnet"],
136
+ features: {
137
+ "solana:signTransaction": {},
138
+ "standard:connect": {},
139
+ },
140
+ });
141
+
142
+ const promise = walletStandardWallets();
143
+ vi.advanceTimersByTime(500);
144
+ const result = await promise;
145
+
146
+ expect(result.solanaWallets[0].info.name).toBe("Trust Wallet");
147
+ });
148
+
149
+ it("should filter out invalid wallets", async () => {
150
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
151
+
152
+ mockWallets.push({
153
+ // Missing name
154
+ icon: "icon.png",
155
+ features: {},
156
+ });
157
+
158
+ const promise = walletStandardWallets();
159
+ vi.advanceTimersByTime(500);
160
+ const result = await promise;
161
+
162
+ expect(result.solanaWallets).toHaveLength(0);
163
+ expect(consoleWarnSpy).toHaveBeenCalled();
164
+ });
165
+
166
+ it("should handle icon as array", async () => {
167
+ mockWallets.push({
168
+ name: "Solana Wallet",
169
+ icon: ["icon1.png", "icon2.png"],
170
+ chains: ["solana:mainnet"],
171
+ features: {
172
+ "solana:signTransaction": {},
173
+ "standard:connect": {},
174
+ },
175
+ });
176
+
177
+ const promise = walletStandardWallets();
178
+ vi.advanceTimersByTime(500);
179
+ const result = await promise;
180
+
181
+ expect(result.solanaWallets[0].info.icon).toBe("icon1.png");
182
+ });
183
+
184
+ it("should deduplicate wallets by name", async () => {
185
+ mockWallets.push(
186
+ {
187
+ name: "Solana Wallet",
188
+ icon: "icon1.png",
189
+ chains: ["solana:mainnet"],
190
+ features: {
191
+ "solana:signTransaction": {},
192
+ "standard:connect": {},
193
+ },
194
+ },
195
+ {
196
+ name: "Solana Wallet",
197
+ icon: "icon2.png",
198
+ chains: ["solana:mainnet"],
199
+ features: {
200
+ "solana:signTransaction": {},
201
+ "standard:connect": {},
202
+ },
203
+ },
204
+ );
205
+
206
+ const promise = walletStandardWallets();
207
+ vi.advanceTimersByTime(500);
208
+ const result = await promise;
209
+
210
+ expect(result.solanaWallets).toHaveLength(1);
211
+ });
212
+
213
+ it("should handle errors gracefully", async () => {
214
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
215
+
216
+ // Mock getWallets to throw error by making mockWalletsAPI.get throw
217
+ const originalGet = mockWalletsAPI.get;
218
+ mockWalletsAPI.get = () => {
219
+ throw new Error("Get wallets error");
220
+ };
221
+
222
+ const promise = walletStandardWallets();
223
+ vi.advanceTimersByTime(500);
224
+ const result = await promise;
225
+
226
+ // Restore original get
227
+ mockWalletsAPI.get = originalGet;
228
+
229
+ expect(result.solanaWallets).toEqual([]);
230
+ expect(result.aptosWallets).toEqual([]);
231
+ expect(result.suiWallets).toEqual([]);
232
+ expect(consoleWarnSpy).toHaveBeenCalled();
233
+ });
234
+
235
+ it("should handle wallet processing errors gracefully", async () => {
236
+ const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
237
+
238
+ mockWallets.push({
239
+ name: "Error Wallet",
240
+ icon: "icon.png",
241
+ get chains() {
242
+ throw new Error("Error accessing chains");
243
+ },
244
+ features: {},
245
+ });
246
+
247
+ const promise = walletStandardWallets();
248
+ vi.advanceTimersByTime(500);
249
+ const result = await promise;
250
+
251
+ expect(result.solanaWallets).toHaveLength(0);
252
+ expect(consoleWarnSpy).toHaveBeenCalled();
253
+ });
254
+
255
+ it("should use rdns as uuid when available", async () => {
256
+ mockWallets.push({
257
+ name: "Solana Wallet",
258
+ icon: "icon.png",
259
+ rdns: "com.solana.wallet",
260
+ homepage: "https://solana.wallet",
261
+ chains: ["solana:mainnet"],
262
+ features: {
263
+ "solana:signTransaction": {},
264
+ "solana:signMessage": {},
265
+ "standard:connect": {
266
+ connect: vi.fn(),
267
+ },
268
+ },
269
+ });
270
+
271
+ const promise = walletStandardWallets();
272
+ vi.advanceTimersByTime(500);
273
+ const result = await promise;
274
+
275
+ expect(result.solanaWallets.length).toBeGreaterThan(0);
276
+ expect(result.solanaWallets[0].info.uuid).toBe("com.solana.wallet");
277
+ });
278
+
279
+ it("should use encoded name as uuid when rdns is missing", async () => {
280
+ mockWallets.push({
281
+ name: "Solana Wallet",
282
+ icon: "icon.png",
283
+ homepage: "https://solana.wallet",
284
+ chains: ["solana:mainnet"],
285
+ features: {
286
+ "solana:signTransaction": {},
287
+ "solana:signMessage": {},
288
+ "standard:connect": {
289
+ connect: vi.fn(),
290
+ },
291
+ },
292
+ });
293
+
294
+ const promise = walletStandardWallets();
295
+ vi.advanceTimersByTime(500);
296
+ const result = await promise;
297
+
298
+ expect(result.solanaWallets.length).toBeGreaterThan(0);
299
+ expect(result.solanaWallets[0].info.uuid).toBe(encodeURIComponent("Solana Wallet"));
300
+ });
301
+ });
302
+ });
package/src/index.ts CHANGED
@@ -1,28 +1,28 @@
1
1
  import { AdaptorChainType, Connector, WalletConnectorType } from "./type";
2
2
  import { isMobile } from "./utils/isMobile";
3
3
  import { uniqueConnectors } from "./utils/utils";
4
+ import { isWagmiConnector } from "./utils/wallet-config";
5
+ import { getAllWallets, walletBaseUrl } from "./wallets";
4
6
  import { getDefaultConnectors } from "./wallets/defaultConnectors";
5
7
  import { connectorDector, supportedWalletConfigTypes, walletConfigAdapter } from "./wallets/detector";
6
- import { WagmiWalletConfig, WalletConfig } from "./wallets/Wallet";
8
+ import { WagmiWalletConfig, WalletConfig } from "./wallets/types";
7
9
  import { eip6963Wallets } from "./wallets/wallet-eip6963";
8
10
  import { walletStandardWallets } from "./wallets/wallet-standard";
9
11
  import { setWalletConnectConfig, walletConnectWallets } from "./wallets/wallet-walletconnect";
10
12
  export * from "./type";
11
- export { setWalletConnectConfig };
13
+ export { isWagmiConnector, setWalletConnectConfig };
12
14
  export type { WalletConfig };
13
15
 
14
- const walletBaseUrl = "https://embedded-wallet.tomo.inc";
15
-
16
16
  export async function loadConnectors({
17
17
  chainType = "all",
18
- recommonedConnectors,
18
+ recommendedConnectors,
19
19
  connectorTypes = [],
20
20
  options = {
21
21
  baseUrl: walletBaseUrl,
22
22
  },
23
23
  }: {
24
24
  chainType?: AdaptorChainType;
25
- recommonedConnectors?: (WalletConfig | WagmiWalletConfig)[];
25
+ recommendedConnectors?: (WalletConfig | WagmiWalletConfig)[];
26
26
  connectorTypes?: WalletConnectorType[];
27
27
  options?: {
28
28
  baseUrl?: string;
@@ -31,7 +31,7 @@ export async function loadConnectors({
31
31
  all: Connector[];
32
32
  recommoned: Connector[];
33
33
  }> {
34
- const recommonedWalletConfigs = recommonedConnectors || [];
34
+ const recommonedWalletConfigs = recommendedConnectors || [];
35
35
 
36
36
  const evmWallets = await eip6963Wallets();
37
37
  const { solanaWallets, aptosWallets } = (await walletStandardWallets()) as {
@@ -42,16 +42,25 @@ export async function loadConnectors({
42
42
  // WalletConnect wallets from WalletConnect Cloud Explorer API
43
43
  const wcWallets = await walletConnectWallets(options.baseUrl);
44
44
 
45
- let recommonedConnectorsDetected: Connector[] = [];
45
+ const allWallets = await getAllWallets(options.baseUrl);
46
+
47
+ let recommendedConnectorsDetected: Connector[] = [];
46
48
  if (recommonedWalletConfigs.length > 0) {
47
- recommonedConnectorsDetected = recommonedWalletConfigs.map((walletConfig, index) => {
49
+ // pass defaultConnectors to walletConfigAdapter, for finding missing properties
50
+ recommendedConnectorsDetected = recommonedWalletConfigs.map((walletConfig, index) => {
48
51
  const connectorType = connectorTypes[index];
49
52
  if (!supportedWalletConfigTypes[connectorType]) {
50
53
  throw new Error(`Unsupported wallet config type: ${connectorType}`);
51
54
  }
52
- const _walletConfig = walletConfigAdapter(walletConfig, connectorType);
55
+ // for finding missing properties when converting wagmi connector with defaultConnectors,
56
+ const _walletConfig = walletConfigAdapter(
57
+ walletConfig,
58
+ connectorType,
59
+ allWallets,
60
+ options.baseUrl || walletBaseUrl,
61
+ );
53
62
  return {
54
- ...connectorDector(_walletConfig),
63
+ ...connectorDector(_walletConfig, connectorType),
55
64
  recommoned: true,
56
65
  };
57
66
  });
@@ -59,7 +68,7 @@ export async function loadConnectors({
59
68
 
60
69
  const defaultConnectors = await getDefaultConnectors(options.baseUrl);
61
70
 
62
- const connectorsFromConfig = uniqueConnectors([...recommonedConnectorsDetected, ...defaultConnectors]);
71
+ const connectorsFromConfig = uniqueConnectors([...recommendedConnectorsDetected, ...defaultConnectors]);
63
72
 
64
73
  // Include WalletConnect wallets along with other detected wallets
65
74
  const connectorsDetecteds = [...evmWallets, ...solanaWallets, ...aptosWallets, ...wcWallets];
@@ -68,14 +77,14 @@ export async function loadConnectors({
68
77
  const _connector = connectorsFromConfig.find((c) => c.info.name === connector.info.name);
69
78
  if (_connector) {
70
79
  _connector.isInstalled = true;
71
- _connector.providers = { ..._connector.providers, ...connector.providers };
80
+ _connector.connectors = { ..._connector.connectors, ...connector.connectors };
72
81
  } else {
73
82
  connectorsFromConfig.push(connector);
74
83
  }
75
84
  }
76
85
 
77
86
  const allConnectors = connectorsFromConfig
78
- .filter((wallet) => (chainType !== "all" ? !!wallet.providers[chainType] : true))
87
+ .filter((wallet) => (chainType !== "all" ? !!wallet.connectors[chainType] : true))
79
88
  .sort((a, b) => {
80
89
  // First priority: recommoned wallets
81
90
  if (a.recommoned && !b.recommoned) return -1;
@@ -91,7 +100,7 @@ export async function loadConnectors({
91
100
 
92
101
  return {
93
102
  all: allConnectors,
94
- recommoned: recommonedConnectorsDetected,
103
+ recommoned: recommendedConnectorsDetected,
95
104
  };
96
105
  }
97
106
 
package/src/type.ts CHANGED
@@ -1,24 +1,9 @@
1
+ import { ChainTypeEnum, ProviderProtocol, ProviderStandard } from "@tomo-inc/wallet-utils";
1
2
  export type WalletProvider = any;
2
3
 
3
- export enum ProviderProtocol {
4
- EIP6963 = "eip6963",
5
- WALLET_STANDARD = "wallet-standard",
6
- WALLET_CONNECT = "wallet-connect",
7
- INJECT = "inject",
8
- }
9
-
10
- export enum ProviderChainType {
11
- inject = "inject",
12
- deeplink = "deeplink",
13
- walletConnect = "walletConnect",
14
- }
15
-
16
4
  export type WalletConnectorType = "wagmi" | "tomo";
17
5
 
18
- export type AdaptorChainType = "all" | "evm" | "solana" | "aptos" | "dogecoin";
19
-
20
- export const SupportedChainTypes = ["evm", "solana", "aptos", "dogecoin"];
21
-
6
+ export type AdaptorChainType = ChainTypeEnum | "all";
22
7
  export interface WalletInfo {
23
8
  uuid: string;
24
9
  name: string;
@@ -53,21 +38,17 @@ export interface WalletInfo {
53
38
  }
54
39
 
55
40
  export interface ConnectorProvider {
56
- provider: any;
41
+ provider: WalletProvider;
57
42
  protocol: ProviderProtocol;
43
+ standard: ProviderStandard;
58
44
  }
59
45
 
46
+ export type ConnectorProviders = Partial<Record<ChainTypeEnum, ConnectorProvider | null>>;
60
47
  export interface Connector {
61
48
  info: WalletInfo;
62
49
  isInstalled?: boolean;
63
50
  recommoned?: boolean;
64
-
65
- providers: {
66
- evm?: ConnectorProvider;
67
- solana?: ConnectorProvider;
68
- aptos?: ConnectorProvider;
69
- dogecoin?: ConnectorProvider;
70
- };
51
+ connectors: Partial<Record<ChainTypeEnum, ConnectorProvider | null>>;
71
52
  }
72
53
 
73
54
  export interface ConnectParams {
@@ -0,0 +1,85 @@
1
+ import { WagmiWalletConfig, WalletConfig } from "../wallets/types";
2
+
3
+ /**
4
+ * Check if the object has key WalletConfig properties
5
+ * WalletConfig requires: id, name, icon, iconBackground
6
+ */
7
+ function hasWalletConfigProperties(config: any): boolean {
8
+ if (!config || typeof config !== "object") {
9
+ return false;
10
+ }
11
+
12
+ // Check for required WalletConfig properties
13
+ return (
14
+ typeof config.id === "string" &&
15
+ typeof config.name === "string" &&
16
+ typeof config.icon === "string" &&
17
+ typeof config.iconBackground === "string"
18
+ );
19
+ }
20
+
21
+ /**
22
+ * Check if the object has wagmi connector characteristics
23
+ * Wagmi connector can be:
24
+ * 1. A function (CreateConnectorFn)
25
+ * 2. An object with createConnector method
26
+ */
27
+ function hasWagmiConnectorProperties(config: any): boolean {
28
+ if (!config || typeof config !== "object") {
29
+ return false;
30
+ }
31
+
32
+ // Check if it has createConnector method (wagmi connector wrapper)
33
+ if (typeof config.createConnector === "function") {
34
+ return true;
35
+ }
36
+
37
+ // Check if it's a wagmi connector instance (has connector-like properties)
38
+ // Wagmi connectors typically have properties like: id, name, type, connect, disconnect, etc.
39
+ const hasConnectorMethods =
40
+ typeof config.connect === "function" ||
41
+ typeof config.disconnect === "function" ||
42
+ typeof config.getAccounts === "function" ||
43
+ typeof config.getChainId === "function";
44
+
45
+ const hasConnectorId = typeof config.id === "string" || typeof config.name === "string";
46
+
47
+ // If it has connector methods but lacks WalletConfig properties, it's likely a wagmi connector
48
+ return hasConnectorMethods && hasConnectorId;
49
+ }
50
+
51
+ /**
52
+ * Detect if the given config is a wagmi connector
53
+ * Wagmi connector is usually a function (CreateConnectorFn) with specific properties
54
+ *
55
+ * @param config - The wallet config to check (WalletConfig | WagmiWalletConfig)
56
+ * @returns true if the config is a wagmi connector, false otherwise
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * import { isWagmiConnector } from '@tomo-inc/wallet-adaptor-base';
61
+ *
62
+ * const connector = injected({ target: 'metamask' });
63
+ * if (isWagmiConnector(connector)) {
64
+ * // Handle wagmi connector
65
+ * }
66
+ * ```
67
+ */
68
+ export function isWagmiConnector(config: WalletConfig | WagmiWalletConfig): boolean {
69
+ // If it's a function, it's likely a wagmi connector (CreateConnectorFn)
70
+ if (typeof config === "function") {
71
+ return true;
72
+ }
73
+
74
+ // If it's an object, check for wagmi connector characteristics
75
+ if (config && typeof config === "object") {
76
+ // Wagmi connector functions usually don't have WalletConfig properties like id, name, etc.
77
+ const hasWalletConfigProps = hasWalletConfigProperties(config);
78
+ // If missing key WalletConfig properties, check for wagmi connector characteristics
79
+ if (!hasWalletConfigProps) {
80
+ return hasWagmiConnectorProperties(config);
81
+ }
82
+ }
83
+
84
+ return false;
85
+ }
@@ -31,9 +31,16 @@ export const connect = async (
31
31
  let address = "";
32
32
  let network = "";
33
33
  if (chainType === "evm") {
34
- const res = await provider.request({ method: "eth_requestAccounts" });
35
- address = res?.[0] || "";
36
- chainId = await provider.request({ method: "eth_chainId" });
34
+ if (provider?.request) {
35
+ const res = await provider.request({ method: "eth_requestAccounts" });
36
+ address = res?.[0] || "";
37
+ chainId = await provider.request({ method: "eth_chainId" });
38
+ } else {
39
+ const wagmiProvider = await provider.getProvider();
40
+ const res = await wagmiProvider.request({ method: "eth_requestAccounts" });
41
+ address = res?.[0] || "";
42
+ chainId = await provider.request({ method: "eth_chainId" });
43
+ }
37
44
  }
38
45
 
39
46
  // console.log("connect request:", chainType, provider);
@@ -1,16 +1,11 @@
1
1
  import { connectorDector } from "./detector";
2
-
3
- let defaultWallets: Record<string, any>[] = [];
2
+ import { getAllWallets, walletBaseUrl } from "./index";
4
3
 
5
4
  export const getDefaultConnectors = async (baseUrl?: string) => {
6
- if (defaultWallets.length > 0) {
7
- return defaultWallets.map((wallet: any) => connectorDector(wallet));
8
- }
9
- const walletListResponse = await fetch((baseUrl || "https://web3-assets.tomo.inc") + "/api/wallets");
10
- const walletList = await walletListResponse.json();
11
- defaultWallets = walletList?.data.map((wallet: any) => ({
5
+ const walletList = await getAllWallets(baseUrl);
6
+ const defaultWallets = walletList?.map((wallet: any) => ({
12
7
  ...wallet,
13
- icon: (baseUrl || "https://web3-assets.tomo.inc") + wallet.icon,
8
+ icon: (baseUrl || walletBaseUrl) + wallet.icon,
14
9
  }));
15
- return defaultWallets?.map((wallet: any) => connectorDector(wallet));
10
+ return defaultWallets?.map((wallet: any) => connectorDector(wallet, "tomo"));
16
11
  };