@multiplatform.one/web3 7.29.1 → 7.30.1
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.
- package/README.md +6 -2
- package/dist/cjs/connectkit/connectors/coinbaseWalletConnector.cjs +79 -0
- package/dist/cjs/connectkit/connectors/coinbaseWalletConnector.native.js +101 -0
- package/dist/cjs/connectkit/connectors/coinbaseWalletConnector.native.js.map +1 -0
- package/dist/cjs/connectkit/connectors/metaMaskConnectConnector.cjs +37 -2
- package/dist/cjs/connectkit/connectors/metaMaskConnectConnector.native.js +38 -2
- package/dist/cjs/connectkit/connectors/metaMaskConnectConnector.native.js.map +1 -1
- package/dist/cjs/connectkit/defaultConnectors.cjs +2 -2
- package/dist/cjs/connectkit/defaultConnectors.native.js +2 -2
- package/dist/cjs/connectkit/defaultConnectors.native.js.map +1 -1
- package/dist/esm/connectkit/connectors/coinbaseWalletConnector.mjs +42 -0
- package/dist/esm/connectkit/connectors/coinbaseWalletConnector.mjs.map +1 -0
- package/dist/esm/connectkit/connectors/coinbaseWalletConnector.native.js +61 -0
- package/dist/esm/connectkit/connectors/coinbaseWalletConnector.native.js.map +1 -0
- package/dist/esm/connectkit/connectors/metaMaskConnectConnector.mjs +37 -3
- package/dist/esm/connectkit/connectors/metaMaskConnectConnector.mjs.map +1 -1
- package/dist/esm/connectkit/connectors/metaMaskConnectConnector.native.js +38 -3
- package/dist/esm/connectkit/connectors/metaMaskConnectConnector.native.js.map +1 -1
- package/dist/esm/connectkit/defaultConnectors.mjs +2 -2
- package/dist/esm/connectkit/defaultConnectors.mjs.map +1 -1
- package/dist/esm/connectkit/defaultConnectors.native.js +2 -2
- package/dist/esm/connectkit/defaultConnectors.native.js.map +1 -1
- package/dist/jsx/connectkit/connectors/coinbaseWalletConnector.mjs +42 -0
- package/dist/jsx/connectkit/connectors/coinbaseWalletConnector.mjs.map +1 -0
- package/dist/jsx/connectkit/connectors/coinbaseWalletConnector.native.js +101 -0
- package/dist/jsx/connectkit/connectors/coinbaseWalletConnector.native.js.map +1 -0
- package/dist/jsx/connectkit/connectors/metaMaskConnectConnector.mjs +37 -3
- package/dist/jsx/connectkit/connectors/metaMaskConnectConnector.mjs.map +1 -1
- package/dist/jsx/connectkit/connectors/metaMaskConnectConnector.native.js +38 -2
- package/dist/jsx/connectkit/connectors/metaMaskConnectConnector.native.js.map +1 -1
- package/dist/jsx/connectkit/defaultConnectors.mjs +2 -2
- package/dist/jsx/connectkit/defaultConnectors.mjs.map +1 -1
- package/dist/jsx/connectkit/defaultConnectors.native.js +2 -2
- package/dist/jsx/connectkit/defaultConnectors.native.js.map +1 -1
- package/dist/keycloak/login.iife.js +303 -303
- package/package.json +7 -6
- package/src/connectkit/connectors/coinbaseWalletConnector.spec.ts +141 -0
- package/src/connectkit/connectors/coinbaseWalletConnector.ts +71 -0
- package/src/connectkit/connectors/metaMaskConnectConnector.spec.ts +179 -3
- package/src/connectkit/connectors/metaMaskConnectConnector.ts +78 -2
- package/src/connectkit/defaultConnectors.ts +3 -2
- package/src/keycloak/useWalletLogin.spec.tsx +15 -2
- package/types/connectkit/connectors/coinbaseWalletConnector.d.ts +5 -0
- package/types/connectkit/connectors/coinbaseWalletConnector.d.ts.map +1 -0
- package/types/connectkit/connectors/metaMaskConnectConnector.d.ts +8 -0
- package/types/connectkit/connectors/metaMaskConnectConnector.d.ts.map +1 -1
- package/types/connectkit/defaultConnectors.d.ts +1 -1
- package/types/connectkit/defaultConnectors.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/web3",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.30.1",
|
|
4
4
|
"description": "Web3 wallet integration for multiplatform.one ecosystem with ConnectKit and wagmi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"connectkit",
|
|
@@ -112,11 +112,12 @@
|
|
|
112
112
|
},
|
|
113
113
|
"dependencies": {
|
|
114
114
|
"@aave/account": "^0.2.0",
|
|
115
|
+
"@coinbase/wallet-sdk": "^4.3.6",
|
|
115
116
|
"@metamask/connect-evm": "^2.1.1",
|
|
116
|
-
"@multiplatform.one/connectkit": "7.
|
|
117
|
-
"@multiplatform.one/gpg": "7.
|
|
118
|
-
"@multiplatform.one/platform": "7.
|
|
119
|
-
"@multiplatform.one/theme": "7.
|
|
117
|
+
"@multiplatform.one/connectkit": "7.30.1",
|
|
118
|
+
"@multiplatform.one/gpg": "7.30.1",
|
|
119
|
+
"@multiplatform.one/platform": "7.30.1",
|
|
120
|
+
"@multiplatform.one/theme": "7.30.1",
|
|
120
121
|
"@noble/curves": "^1.9.7",
|
|
121
122
|
"@solana/kit": "^8.3.0",
|
|
122
123
|
"@solana/wallet-adapter-base": "^0.9.27",
|
|
@@ -138,7 +139,7 @@
|
|
|
138
139
|
"wagmi": "^3.7.1"
|
|
139
140
|
},
|
|
140
141
|
"devDependencies": {
|
|
141
|
-
"@multiplatform.one/config": "7.
|
|
142
|
+
"@multiplatform.one/config": "7.30.1",
|
|
142
143
|
"@tamagui/build": "2.7.6",
|
|
143
144
|
"@tanstack/react-query": "^5.100.7",
|
|
144
145
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { createConfig, createStorage } from "wagmi";
|
|
3
|
+
import { reconnect } from "wagmi/actions";
|
|
4
|
+
import { mainnet } from "viem/chains";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
const { sdkLoaded } = vi.hoisted(() => ({ sdkLoaded: vi.fn() }));
|
|
8
|
+
|
|
9
|
+
vi.mock("@metamask/connect-evm", () => ({ createEVMClient: vi.fn(() => new Promise(() => {})) }));
|
|
10
|
+
vi.mock("@coinbase/wallet-sdk", async (importOriginal) => {
|
|
11
|
+
sdkLoaded();
|
|
12
|
+
return importOriginal();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
import getDefaultConfig from "../defaultConfig";
|
|
16
|
+
import { coinbaseWalletConnectorId } from "./coinbaseWalletConnector";
|
|
17
|
+
|
|
18
|
+
const loginPath = "/realms/main/protocol/openid-connect/auth";
|
|
19
|
+
|
|
20
|
+
const memory = () => {
|
|
21
|
+
const store = new Map<string, string>();
|
|
22
|
+
return {
|
|
23
|
+
getItem: (key: string) => store.get(key) ?? null,
|
|
24
|
+
setItem: (key: string, value: string) => void store.set(key, value),
|
|
25
|
+
removeItem: (key: string) => void store.delete(key),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// The Coinbase Wallet SDK reads the page's Cross-Origin-Opener-Policy with a HEAD
|
|
30
|
+
// request for the page's own path the moment it is built. Keycloak's auth endpoint
|
|
31
|
+
// answers that 400, since the request carries none of its query.
|
|
32
|
+
const fetchSpy = vi.fn(
|
|
33
|
+
async (_input: RequestInfo | URL, _init?: RequestInit) => new Response(null, { status: 400 }),
|
|
34
|
+
);
|
|
35
|
+
const pageHeads = () =>
|
|
36
|
+
fetchSpy.mock.calls.filter(
|
|
37
|
+
([url, init]) => String(url).endsWith(loginPath) && init?.method === "HEAD",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
function setup(storage = createStorage({ storage: memory() })) {
|
|
41
|
+
const config = createConfig({
|
|
42
|
+
...getDefaultConfig({ appName: "Sign in", chains: [mainnet], enableWallet: false }),
|
|
43
|
+
storage,
|
|
44
|
+
});
|
|
45
|
+
const coinbase = config.connectors.find((c) => c.id === coinbaseWalletConnectorId)!;
|
|
46
|
+
return { config, coinbase };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("Coinbase Wallet connector", () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
vi.resetModules();
|
|
52
|
+
sdkLoaded.mockClear();
|
|
53
|
+
fetchSpy.mockClear();
|
|
54
|
+
vi.stubGlobal("fetch", fetchSpy);
|
|
55
|
+
vi.spyOn(window, "open").mockReturnValue(null);
|
|
56
|
+
window.history.replaceState(null, "", `${loginPath}?client_id=quickcam`);
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
vi.unstubAllGlobals();
|
|
60
|
+
vi.restoreAllMocks();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("loads the SDK's module as the config is built, without a HEAD", async () => {
|
|
64
|
+
setup();
|
|
65
|
+
await vi.waitFor(() => expect(sdkLoaded).toHaveBeenCalledOnce());
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
67
|
+
expect(pageHeads()).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("sends no HEAD for a Keycloak login page when wagmi reconnects on load", async () => {
|
|
71
|
+
const { config, coinbase } = setup();
|
|
72
|
+
expect(coinbase).toBeDefined();
|
|
73
|
+
await reconnect(config, { connectors: [coinbase] });
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
75
|
+
expect(pageHeads()).toEqual([]);
|
|
76
|
+
expect(await coinbase.getProvider()).toBeUndefined();
|
|
77
|
+
expect(await coinbase.isAuthorized()).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("builds the SDK when the user picks Coinbase Wallet", async () => {
|
|
81
|
+
const { coinbase } = setup();
|
|
82
|
+
void coinbase.connect().catch(() => undefined);
|
|
83
|
+
await vi.waitFor(() => expect(pageHeads()).toHaveLength(1));
|
|
84
|
+
expect(await coinbase.getProvider()).toBeDefined();
|
|
85
|
+
await vi.waitFor(() => expect(window.open).toHaveBeenCalled());
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("keeps a connect the user started while the load's storage read was pending", async () => {
|
|
89
|
+
let release!: (value: string | null) => void;
|
|
90
|
+
const recent = new Promise<string | null>((resolve) => (release = resolve));
|
|
91
|
+
const base = memory();
|
|
92
|
+
const { coinbase } = setup(
|
|
93
|
+
createStorage({
|
|
94
|
+
storage: {
|
|
95
|
+
...base,
|
|
96
|
+
getItem: (key: string) =>
|
|
97
|
+
key.endsWith("recentConnectorId") ? recent : base.getItem(key),
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
const onLoad = coinbase.getProvider();
|
|
102
|
+
void coinbase.connect().catch(() => undefined);
|
|
103
|
+
await vi.waitFor(() => expect(pageHeads()).toHaveLength(1));
|
|
104
|
+
release(null);
|
|
105
|
+
expect(await onLoad).toBeDefined();
|
|
106
|
+
expect(await coinbase.getProvider()).toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("builds the SDK to restore a Coinbase Wallet connection wagmi still holds", async () => {
|
|
110
|
+
const storage = createStorage({ storage: memory() });
|
|
111
|
+
await storage.setItem("recentConnectorId", "metaMaskSDK");
|
|
112
|
+
// The page before the reload: a Coinbase Wallet connection wagmi persisted, current
|
|
113
|
+
// again after a later wallet closed, with recentConnectorId still naming that wallet.
|
|
114
|
+
const before = setup(storage).config;
|
|
115
|
+
const connector = {
|
|
116
|
+
id: coinbaseWalletConnectorId,
|
|
117
|
+
name: "Coinbase Wallet",
|
|
118
|
+
type: "coinbaseWallet",
|
|
119
|
+
uid: "cb",
|
|
120
|
+
};
|
|
121
|
+
before.setState((state) => ({
|
|
122
|
+
...state,
|
|
123
|
+
connections: new Map([
|
|
124
|
+
["cb", { accounts: ["0x0000000000000000000000000000000000000001"], chainId: 1, connector }],
|
|
125
|
+
]) as never,
|
|
126
|
+
current: "cb",
|
|
127
|
+
}));
|
|
128
|
+
fetchSpy.mockClear();
|
|
129
|
+
const { coinbase } = setup(storage);
|
|
130
|
+
expect(await coinbase.getProvider()).toBeDefined();
|
|
131
|
+
expect(pageHeads()).toHaveLength(1);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("builds the SDK to reconnect when Coinbase Wallet was the last connector", async () => {
|
|
135
|
+
const storage = createStorage({ storage: memory() });
|
|
136
|
+
await storage.setItem("recentConnectorId", coinbaseWalletConnectorId);
|
|
137
|
+
const { coinbase } = setup(storage);
|
|
138
|
+
expect(await coinbase.getProvider()).toBeDefined();
|
|
139
|
+
expect(pageHeads()).toHaveLength(1);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// wagmi's coinbaseWallet() builds the Coinbase Wallet SDK in getProvider(), and
|
|
2
|
+
// wagmi's reconnect on load asks every connector for its provider. Building the
|
|
3
|
+
// SDK sends a HEAD request for the page's own path to read its
|
|
4
|
+
// Cross-Origin-Opener-Policy. On a Keycloak login page that path is the auth
|
|
5
|
+
// endpoint, which answers 400 without its query, so every load logged two console
|
|
6
|
+
// errors (PROMA-123). This builds the SDK only when the user picks Coinbase
|
|
7
|
+
// Wallet, or when the page restores a Coinbase Wallet connection: the last one
|
|
8
|
+
// wagmi connected, or one wagmi still holds.
|
|
9
|
+
import type { CreateConnectorFn, Storage } from "wagmi";
|
|
10
|
+
import { coinbaseWallet, type CoinbaseWalletParameters } from "wagmi/connectors/coinbaseWallet";
|
|
11
|
+
|
|
12
|
+
export const coinbaseWalletConnectorId = "coinbaseWalletSDK";
|
|
13
|
+
|
|
14
|
+
type PersistedConnection = { connector?: { id?: string } };
|
|
15
|
+
|
|
16
|
+
// wagmi persists its connections under "store" as a Map, which its storage
|
|
17
|
+
// revives on read.
|
|
18
|
+
async function heldConnectorIds(storage: Storage | null | undefined): Promise<string[]> {
|
|
19
|
+
const persisted = (await storage?.getItem("store" as never)) as
|
|
20
|
+
| { state?: { connections?: unknown } }
|
|
21
|
+
| null
|
|
22
|
+
| undefined;
|
|
23
|
+
const connections = persisted?.state?.connections;
|
|
24
|
+
if (!(connections instanceof Map)) return [];
|
|
25
|
+
return [...(connections as Map<string, PersistedConnection>).values()].flatMap(
|
|
26
|
+
(connection) => connection?.connector?.id ?? [],
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function coinbaseWalletConnector(parameters?: CoinbaseWalletParameters): CreateConnectorFn {
|
|
31
|
+
const base = coinbaseWallet(parameters);
|
|
32
|
+
|
|
33
|
+
return (config) => {
|
|
34
|
+
const connector = base(config);
|
|
35
|
+
// Read while the connector is built: createConfig then writes its initial state
|
|
36
|
+
// over the persisted one, which stays empty until wagmi rehydrates.
|
|
37
|
+
const heldOnLoad = heldConnectorIds(config.storage).catch((): string[] => []);
|
|
38
|
+
let picked = false;
|
|
39
|
+
const due = async () => {
|
|
40
|
+
if (picked) return true;
|
|
41
|
+
const [recent, held] = await Promise.all([
|
|
42
|
+
config.storage?.getItem("recentConnectorId"),
|
|
43
|
+
heldOnLoad,
|
|
44
|
+
]);
|
|
45
|
+
if (recent === coinbaseWalletConnectorId || held.includes(coinbaseWalletConnectorId))
|
|
46
|
+
picked = true;
|
|
47
|
+
return picked;
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
...connector,
|
|
51
|
+
async setup() {
|
|
52
|
+
// The SDK opens its popup only inside the tap's user activation, which a cold
|
|
53
|
+
// chunk load between tap and popup can outlast. Loading the module builds no SDK.
|
|
54
|
+
if (typeof window !== "undefined")
|
|
55
|
+
void import("@coinbase/wallet-sdk").catch(() => undefined);
|
|
56
|
+
},
|
|
57
|
+
async connect(parameters) {
|
|
58
|
+
picked = true;
|
|
59
|
+
return connector.connect.call(this, parameters);
|
|
60
|
+
},
|
|
61
|
+
async getProvider(parameters) {
|
|
62
|
+
if (!(await due())) return undefined;
|
|
63
|
+
return connector.getProvider.call(this, parameters);
|
|
64
|
+
},
|
|
65
|
+
async isAuthorized() {
|
|
66
|
+
if (!(await due())) return false;
|
|
67
|
+
return connector.isAuthorized.call(this);
|
|
68
|
+
},
|
|
69
|
+
} as ReturnType<typeof base>;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { EventHandlers } from "@metamask/connect-evm";
|
|
2
2
|
import type { Chain } from "viem";
|
|
3
3
|
import { getAddress, SwitchChainError } from "viem";
|
|
4
|
-
import { base, mainnet, polygon } from "viem/chains";
|
|
4
|
+
import { base, baseSepolia, mainnet, polygon, polygonAmoy } from "viem/chains";
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
import { createConfig, http } from "wagmi";
|
|
7
|
-
import { connect, reconnect } from "wagmi/actions";
|
|
7
|
+
import { connect, reconnect, sendTransaction, signMessage, signTypedData } from "wagmi/actions";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
metaMaskConnect,
|
|
@@ -122,6 +122,80 @@ describe("metaMaskConnect", () => {
|
|
|
122
122
|
expect(result.chainId).toBe(base.id);
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
+
it("tells MetaMask about Ethereum mainnet, which it adds to every connect, after the app's chains", async () => {
|
|
126
|
+
const connector = build([baseSepolia, polygonAmoy]);
|
|
127
|
+
await connector.setup?.();
|
|
128
|
+
await connector.connect({ chainId: baseSepolia.id });
|
|
129
|
+
|
|
130
|
+
const options = createEVMClient.mock.calls[0]![0] as {
|
|
131
|
+
api: { supportedNetworks: Record<string, string> };
|
|
132
|
+
};
|
|
133
|
+
expect(Object.keys(options.api.supportedNetworks)).toEqual([
|
|
134
|
+
hex(baseSepolia.id),
|
|
135
|
+
hex(polygonAmoy.id),
|
|
136
|
+
hex(mainnet.id),
|
|
137
|
+
]);
|
|
138
|
+
expect(options.api.supportedNetworks[hex(mainnet.id)]).toBe(mainnet.rpcUrls.default.http[0]);
|
|
139
|
+
expect(client.connect.mock.calls[0]![0].chainIds).toEqual([
|
|
140
|
+
hex(baseSepolia.id),
|
|
141
|
+
hex(polygonAmoy.id),
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("falls back to mainnet's default URL when the app's mainnet transport has no usable one", async () => {
|
|
146
|
+
const connector = metaMaskConnect({ dapp: { name: "test", url: "https://example.test" } })({
|
|
147
|
+
chains: [mainnet, baseSepolia],
|
|
148
|
+
emitter: { uid: "metaMaskSDK", emit: vi.fn(), on: vi.fn(), once: vi.fn(), off: vi.fn() },
|
|
149
|
+
transports: { [mainnet.id]: http("/rpc/eth"), [baseSepolia.id]: http() },
|
|
150
|
+
} as never);
|
|
151
|
+
await connector.setup?.();
|
|
152
|
+
|
|
153
|
+
const options = createEVMClient.mock.calls[0]![0] as {
|
|
154
|
+
api: { supportedNetworks: Record<string, string> };
|
|
155
|
+
};
|
|
156
|
+
expect(options.api.supportedNetworks[hex(mainnet.id)]).toBe(mainnet.rpcUrls.default.http[0]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("keeps the app's own mainnet URL when the app lists mainnet", async () => {
|
|
160
|
+
const connector = metaMaskConnect({ dapp: { name: "test", url: "https://example.test" } })({
|
|
161
|
+
chains: [mainnet, base],
|
|
162
|
+
emitter: { uid: "metaMaskSDK", emit: vi.fn(), on: vi.fn(), once: vi.fn(), off: vi.fn() },
|
|
163
|
+
transports: { [mainnet.id]: http("https://mainnet.example.test"), [base.id]: http() },
|
|
164
|
+
} as never);
|
|
165
|
+
await connector.setup?.();
|
|
166
|
+
|
|
167
|
+
const options = createEVMClient.mock.calls[0]![0] as {
|
|
168
|
+
api: { supportedNetworks: Record<string, string> };
|
|
169
|
+
};
|
|
170
|
+
expect(Object.keys(options.api.supportedNetworks)).toEqual([hex(mainnet.id), hex(base.id)]);
|
|
171
|
+
expect(options.api.supportedNetworks[hex(mainnet.id)]).toBe("https://mainnet.example.test");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("takes mainnet's URL from a mainnet transport the app gives without listing mainnet", async () => {
|
|
175
|
+
const connector = metaMaskConnect({ dapp: { name: "test", url: "https://example.test" } })({
|
|
176
|
+
chains: [baseSepolia],
|
|
177
|
+
emitter: { uid: "metaMaskSDK", emit: vi.fn(), on: vi.fn(), once: vi.fn(), off: vi.fn() },
|
|
178
|
+
transports: {
|
|
179
|
+
[baseSepolia.id]: http(),
|
|
180
|
+
[mainnet.id]: http("https://mainnet.example.test"),
|
|
181
|
+
},
|
|
182
|
+
} as never);
|
|
183
|
+
await connector.setup?.();
|
|
184
|
+
|
|
185
|
+
const options = createEVMClient.mock.calls[0]![0] as {
|
|
186
|
+
api: { supportedNetworks: Record<string, string> };
|
|
187
|
+
};
|
|
188
|
+
expect(options.api.supportedNetworks[hex(mainnet.id)]).toBe("https://mainnet.example.test");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("still refuses to switch to mainnet when the app does not list it", async () => {
|
|
192
|
+
const connector = build([baseSepolia]);
|
|
193
|
+
await expect(connector.switchChain!({ chainId: mainnet.id })).rejects.toBeInstanceOf(
|
|
194
|
+
SwitchChainError,
|
|
195
|
+
);
|
|
196
|
+
expect(client.switchChain).not.toHaveBeenCalled();
|
|
197
|
+
});
|
|
198
|
+
|
|
125
199
|
it("refuses to switch MetaMask to a synthetic chain", async () => {
|
|
126
200
|
const connector = build([mainnet, ...syntheticChains]);
|
|
127
201
|
await expect(connector.switchChain!({ chainId: SUI_CHAIN_ID })).rejects.toBeInstanceOf(
|
|
@@ -147,7 +221,11 @@ describe("metaMaskConnect under wagmi", () => {
|
|
|
147
221
|
client.accounts = [];
|
|
148
222
|
});
|
|
149
223
|
|
|
150
|
-
afterEach(() =>
|
|
224
|
+
afterEach(() => {
|
|
225
|
+
resetMetaMaskConnectClient();
|
|
226
|
+
client.getChainId.mockImplementation(() => "0x2105");
|
|
227
|
+
client.getProvider.mockImplementation(() => ({ request: vi.fn() }));
|
|
228
|
+
});
|
|
151
229
|
|
|
152
230
|
function wagmiConfig() {
|
|
153
231
|
return createConfig({
|
|
@@ -221,6 +299,104 @@ describe("metaMaskConnect under wagmi", () => {
|
|
|
221
299
|
expect(config.state.connections.get(connector.uid)?.accounts).toEqual([getAddress(second)]);
|
|
222
300
|
});
|
|
223
301
|
|
|
302
|
+
it("signs on mainnet for a testnet app when the phone wallet granted only mainnet", async () => {
|
|
303
|
+
// MetaMask Connect refuses a request on a chain it was not given.
|
|
304
|
+
const request = vi.fn(async ({ method }: { method: string }) => {
|
|
305
|
+
const { api } = createEVMClient.mock.calls[0]![0] as {
|
|
306
|
+
api: { supportedNetworks: Record<string, string> };
|
|
307
|
+
};
|
|
308
|
+
const chainId = client.getChainId();
|
|
309
|
+
if (!api.supportedNetworks[chainId])
|
|
310
|
+
throw new Error(`Chain eip155:${Number(chainId)} is not configured in supportedNetworks`);
|
|
311
|
+
return method === "personal_sign" ? "0x5167" : null;
|
|
312
|
+
});
|
|
313
|
+
client.getChainId.mockReturnValue("0x1");
|
|
314
|
+
client.getProvider.mockReturnValue({ request } as never);
|
|
315
|
+
client.connect.mockImplementationOnce(async () => ({ accounts: [first], chainId: "0x1" }));
|
|
316
|
+
const config = createConfig({
|
|
317
|
+
chains: [baseSepolia],
|
|
318
|
+
connectors: [metaMaskConnect({ dapp: { name: "test", url: "https://example.test" } })],
|
|
319
|
+
transports: { [baseSepolia.id]: http() },
|
|
320
|
+
multiInjectedProviderDiscovery: false,
|
|
321
|
+
storage: null,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const connected = await connect(config, { connector: config.connectors[0]! });
|
|
325
|
+
const signature = await signMessage(config, { message: "prove it" });
|
|
326
|
+
|
|
327
|
+
expect(connected.chainId).toBe(mainnet.id);
|
|
328
|
+
expect(signature).toBe("0x5167");
|
|
329
|
+
expect(request.mock.calls.map(([call]) => call.method)).toContain("personal_sign");
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("sends no transaction or typed signature on mainnet for an app that does not list it", async () => {
|
|
333
|
+
const request = vi.fn(async ({ method }: { method: string }) =>
|
|
334
|
+
method === "eth_chainId" ? "0x1" : "0xabc",
|
|
335
|
+
);
|
|
336
|
+
client.getChainId.mockReturnValue("0x1");
|
|
337
|
+
client.getProvider.mockReturnValue({ request } as never);
|
|
338
|
+
client.connect.mockImplementationOnce(async () => ({ accounts: [first], chainId: "0x1" }));
|
|
339
|
+
const config = createConfig({
|
|
340
|
+
chains: [baseSepolia],
|
|
341
|
+
connectors: [metaMaskConnect({ dapp: { name: "test", url: "https://example.test" } })],
|
|
342
|
+
transports: { [baseSepolia.id]: http() },
|
|
343
|
+
multiInjectedProviderDiscovery: false,
|
|
344
|
+
storage: null,
|
|
345
|
+
});
|
|
346
|
+
await connect(config, { connector: config.connectors[0]! });
|
|
347
|
+
|
|
348
|
+
await expect(sendTransaction(config, { to: second, value: 1n, gas: 21000n })).rejects.toThrow();
|
|
349
|
+
await expect(
|
|
350
|
+
signTypedData(config, {
|
|
351
|
+
domain: { name: "t", chainId: 1 },
|
|
352
|
+
types: { Mail: [{ name: "body", type: "string" }] },
|
|
353
|
+
primaryType: "Mail",
|
|
354
|
+
message: { body: "x" },
|
|
355
|
+
}),
|
|
356
|
+
).rejects.toThrow();
|
|
357
|
+
|
|
358
|
+
const sent = request.mock.calls.map(([call]) => call.method);
|
|
359
|
+
expect(sent).not.toContain("eth_sendTransaction");
|
|
360
|
+
expect(sent).not.toContain("eth_signTypedData_v4");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("checks each config's own chains on the page's one provider, whichever wraps it first", async () => {
|
|
364
|
+
const request = vi.fn(async ({ method }: { method: string }) =>
|
|
365
|
+
method === "eth_chainId" ? "0x1" : "0xabc",
|
|
366
|
+
);
|
|
367
|
+
const provider = { request };
|
|
368
|
+
client.getChainId.mockReturnValue("0x1");
|
|
369
|
+
client.getProvider.mockReturnValue(provider as never);
|
|
370
|
+
const configFor = (chains: readonly [Chain, ...Chain[]]) =>
|
|
371
|
+
createConfig({
|
|
372
|
+
chains,
|
|
373
|
+
connectors: [metaMaskConnect({ dapp: { name: "test", url: "https://example.test" } })],
|
|
374
|
+
transports: Object.fromEntries(chains.map((chain) => [chain.id, http()])) as never,
|
|
375
|
+
multiInjectedProviderDiscovery: false,
|
|
376
|
+
storage: null,
|
|
377
|
+
});
|
|
378
|
+
const listsMainnet = configFor([mainnet, base]);
|
|
379
|
+
const testnetOnly = configFor([baseSepolia]);
|
|
380
|
+
const send = (config: ReturnType<typeof configFor>) =>
|
|
381
|
+
sendTransaction(config, { to: second, value: 1n, gas: 21000n });
|
|
382
|
+
|
|
383
|
+
client.connect.mockImplementation(async () => ({ accounts: [first], chainId: "0x1" }));
|
|
384
|
+
await connect(listsMainnet, { connector: listsMainnet.connectors[0]! });
|
|
385
|
+
await connect(testnetOnly, { connector: testnetOnly.connectors[0]! });
|
|
386
|
+
|
|
387
|
+
await expect(send(listsMainnet)).resolves.toBe("0xabc");
|
|
388
|
+
await expect(send(testnetOnly)).rejects.toThrow();
|
|
389
|
+
await expect(send(listsMainnet)).resolves.toBe("0xabc");
|
|
390
|
+
expect(
|
|
391
|
+
request.mock.calls.filter(([call]) => call.method === "eth_sendTransaction"),
|
|
392
|
+
).toHaveLength(2);
|
|
393
|
+
client.connect.mockReset();
|
|
394
|
+
client.connect.mockImplementation(async () => ({
|
|
395
|
+
accounts: ["0x0000000000000000000000000000000000000001"],
|
|
396
|
+
chainId: "0x2105",
|
|
397
|
+
}));
|
|
398
|
+
});
|
|
399
|
+
|
|
224
400
|
it("ignores a connect that carries no account", async () => {
|
|
225
401
|
const config = wagmiConfig();
|
|
226
402
|
const events = await clientEvents();
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
SwitchChainError,
|
|
24
24
|
UserRejectedRequestError,
|
|
25
25
|
} from "viem";
|
|
26
|
+
import { mainnet } from "wagmi/chains";
|
|
26
27
|
|
|
27
28
|
import { isSyntheticChainId } from "./syntheticChains";
|
|
28
29
|
|
|
@@ -59,6 +60,73 @@ export function metaMaskNetworks(
|
|
|
59
60
|
return networks;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/**
|
|
64
|
+
* `networks` and Ethereum mainnet, for createEVMClient. MetaMask Connect adds
|
|
65
|
+
* mainnet to every connect, and a phone wallet with none of the app's chains
|
|
66
|
+
* enabled grants only mainnet. The session then sits on chain 1, and MetaMask
|
|
67
|
+
* Connect refuses every request on a chain it was not given, personal_sign
|
|
68
|
+
* included, so the wallet could never prove itself (PROMA-121).
|
|
69
|
+
*/
|
|
70
|
+
export function metaMaskSupportedNetworks(
|
|
71
|
+
networks: Record<Hex, string>,
|
|
72
|
+
transports?: ConnectorConfig["transports"],
|
|
73
|
+
): Record<Hex, string> {
|
|
74
|
+
const id = numberToHex(mainnet.id);
|
|
75
|
+
if (networks[id]) return networks;
|
|
76
|
+
return {
|
|
77
|
+
...networks,
|
|
78
|
+
[id]: metaMaskNetworks([mainnet], transports)[id] ?? mainnet.rpcUrls.default.http[0]!,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// What a session may still ask for on a chain the app does not list, such as
|
|
83
|
+
// the mainnet a phone wallet grants on its own: the wallet's bookkeeping, a
|
|
84
|
+
// switch to a listed chain, and personal_sign, which no chain scopes. A
|
|
85
|
+
// transaction or a typed signature there would reach a network the app never
|
|
86
|
+
// chose.
|
|
87
|
+
const offListMethods = new Set([
|
|
88
|
+
"personal_sign",
|
|
89
|
+
"eth_accounts",
|
|
90
|
+
"eth_coinbase",
|
|
91
|
+
"eth_chainId",
|
|
92
|
+
"eth_requestAccounts",
|
|
93
|
+
"net_version",
|
|
94
|
+
"wallet_getPermissions",
|
|
95
|
+
"wallet_requestPermissions",
|
|
96
|
+
"wallet_revokePermissions",
|
|
97
|
+
"wallet_switchEthereumChain",
|
|
98
|
+
"wallet_addEthereumChain",
|
|
99
|
+
"metamask_getProviderState",
|
|
100
|
+
"metamask_sendDomainMetadata",
|
|
101
|
+
"metamask_logWeb3ShimUsage",
|
|
102
|
+
"wallet_registerOnboarding",
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
function listedChainsOnly(
|
|
106
|
+
provider: EIP1193Provider,
|
|
107
|
+
mm: MetamaskConnectEVM,
|
|
108
|
+
networks: Record<Hex, string>,
|
|
109
|
+
): EIP1193Provider {
|
|
110
|
+
const request = (async (args: { method: string; params?: unknown }) => {
|
|
111
|
+
const chainId = mm.getChainId();
|
|
112
|
+
if (chainId && !networks[numberToHex(Number(chainId))] && !offListMethods.has(args.method))
|
|
113
|
+
throw Object.assign(
|
|
114
|
+
new Error(
|
|
115
|
+
`MetaMask is on chain ${Number(chainId)}, which this app does not list, so ${args.method} was not sent`,
|
|
116
|
+
),
|
|
117
|
+
{ code: 4901 },
|
|
118
|
+
);
|
|
119
|
+
return provider.request(args as never);
|
|
120
|
+
}) as EIP1193Provider["request"];
|
|
121
|
+
return new Proxy(provider, {
|
|
122
|
+
get(target, property) {
|
|
123
|
+
if (property === "request") return request;
|
|
124
|
+
const value = Reflect.get(target, property);
|
|
125
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
62
130
|
/**
|
|
63
131
|
* The URL wagmi's transport for `chain` talks to, the first one of a `fallback`.
|
|
64
132
|
* @wagmi/core's extractRpcUrls does this, and wagmi does not re-export it.
|
|
@@ -174,8 +242,10 @@ export function metaMaskConnect(parameters: MetaMaskConnectParameters) {
|
|
|
174
242
|
return createConnector<EIP1193Provider>((config) => {
|
|
175
243
|
owner ??= config.emitter;
|
|
176
244
|
const networks = metaMaskNetworks(config.chains, config.transports);
|
|
245
|
+
const supportedNetworks = metaMaskSupportedNetworks(networks, config.transports);
|
|
177
246
|
const fallbackChainId = Number(Object.keys(networks)[0] ?? 1);
|
|
178
|
-
const instance = () => getClient(parameters,
|
|
247
|
+
const instance = () => getClient(parameters, supportedNetworks);
|
|
248
|
+
let guarded: { provider: EIP1193Provider; wrapped: EIP1193Provider } | undefined;
|
|
179
249
|
// Every call wagmi makes on the connector it uses claims the client's events;
|
|
180
250
|
// setup() runs for every built config, so it does not.
|
|
181
251
|
const claim = () => {
|
|
@@ -225,7 +295,13 @@ export function metaMaskConnect(parameters: MetaMaskConnectParameters) {
|
|
|
225
295
|
return chainIdOf(await claim());
|
|
226
296
|
},
|
|
227
297
|
async getProvider() {
|
|
228
|
-
|
|
298
|
+
// The page has one provider but each config its own chains, so each
|
|
299
|
+
// connector wraps it for itself.
|
|
300
|
+
const mm = await claim();
|
|
301
|
+
const provider = mm.getProvider();
|
|
302
|
+
if (guarded?.provider !== provider)
|
|
303
|
+
guarded = { provider, wrapped: listedChainsOnly(provider, mm, networks) };
|
|
304
|
+
return guarded.wrapped;
|
|
229
305
|
},
|
|
230
306
|
async isAuthorized() {
|
|
231
307
|
// createEVMClient has already resumed a stored session, so its cached
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CreateConnectorFn } from "wagmi";
|
|
2
2
|
// Per-connector subpaths: the package root re-exports every wagmi connector, and a
|
|
3
3
|
// bundler that doesn't tree-shake (the webview, the login bundles) ships them all.
|
|
4
|
-
import {
|
|
4
|
+
import type { CoinbaseWalletParameters } from "wagmi/connectors/coinbaseWallet";
|
|
5
5
|
import { safe } from "wagmi/connectors/safe";
|
|
6
6
|
import { metaMaskConnect } from "./connectors/metaMaskConnectConnector";
|
|
7
7
|
import {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import type { EthereumProviderOptions as AaveAccountOptions } from "@aave/account";
|
|
13
13
|
import { aaveAccountConnector } from "./connectors/aaveAccountConnector";
|
|
14
|
+
import { coinbaseWalletConnector } from "./connectors/coinbaseWalletConnector";
|
|
14
15
|
|
|
15
16
|
type DefaultConnectorsProps = {
|
|
16
17
|
app: {
|
|
@@ -59,7 +60,7 @@ const defaultConnectors = ({
|
|
|
59
60
|
},
|
|
60
61
|
...(isEmbeddedDesktopWebview() ? metaMaskOptionsForEmbeddedDesktop() : {}),
|
|
61
62
|
}),
|
|
62
|
-
|
|
63
|
+
coinbaseWalletConnector({
|
|
63
64
|
appName: app.name,
|
|
64
65
|
appLogoUrl: app.icon,
|
|
65
66
|
preference: {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
import { act, cleanup, render, waitFor } from "@testing-library/react";
|
|
3
|
-
import { verifyMessage, type Address } from "viem";
|
|
3
|
+
import { http, verifyMessage, type Address } from "viem";
|
|
4
|
+
import { mainnet } from "viem/chains";
|
|
5
|
+
import { createConfig } from "wagmi";
|
|
4
6
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
7
|
import { useConnection } from "../core/react";
|
|
6
8
|
import type { SignInCredential } from "../core/signIn";
|
|
9
|
+
import { eth } from "../families/eth";
|
|
10
|
+
import { createMockConnector } from "../providers/mockConnector";
|
|
7
11
|
import { Web3Provider } from "../providers/WalletProvider";
|
|
8
12
|
import { useWalletLogin, type UseWalletLoginResult } from "./useWalletLogin";
|
|
9
13
|
|
|
@@ -21,9 +25,18 @@ describe("useWalletLogin under a SignInProvider", () => {
|
|
|
21
25
|
live.address = useConnection().address;
|
|
22
26
|
return null;
|
|
23
27
|
}
|
|
28
|
+
// Only the mock: wagmi's restore reports connected after every connector's
|
|
29
|
+
// getProvider settles, and the default ones load wallet SDKs first.
|
|
30
|
+
const config = createConfig({
|
|
31
|
+
chains: [mainnet],
|
|
32
|
+
connectors: [createMockConnector()],
|
|
33
|
+
multiInjectedProviderDiscovery: false,
|
|
34
|
+
transports: { [mainnet.id]: http() },
|
|
35
|
+
});
|
|
24
36
|
render(
|
|
25
37
|
<Web3Provider
|
|
26
|
-
config={{ appName: "wallet login on the store"
|
|
38
|
+
config={{ appName: "wallet login on the store" }}
|
|
39
|
+
families={[eth({ config })]}
|
|
27
40
|
signIn={{ getNonce: async () => "nonce-9", onSign, domain: "app.example.com" }}
|
|
28
41
|
>
|
|
29
42
|
<Probe />
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CreateConnectorFn } from "wagmi";
|
|
2
|
+
import { type CoinbaseWalletParameters } from "wagmi/connectors/coinbaseWallet";
|
|
3
|
+
export declare const coinbaseWalletConnectorId = "coinbaseWalletSDK";
|
|
4
|
+
export declare function coinbaseWalletConnector(parameters?: CoinbaseWalletParameters): CreateConnectorFn;
|
|
5
|
+
//# sourceMappingURL=coinbaseWalletConnector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coinbaseWalletConnector.d.ts","sourceRoot":"","sources":["../../../src/connectkit/connectors/coinbaseWalletConnector.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAW,MAAM,OAAO,CAAC;AACxD,OAAO,EAAkB,KAAK,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAEhG,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAkB7D,wBAAgB,uBAAuB,CAAC,UAAU,CAAC,EAAE,wBAAwB,GAAG,iBAAiB,CAyChG"}
|
|
@@ -23,6 +23,14 @@ type ConnectorConfig = Parameters<CreateConnectorFn>[0];
|
|
|
23
23
|
* kills connect() before it ever emits `display_uri`.
|
|
24
24
|
*/
|
|
25
25
|
export declare function metaMaskNetworks(chains: readonly Chain[], transports?: ConnectorConfig["transports"]): Record<Hex, string>;
|
|
26
|
+
/**
|
|
27
|
+
* `networks` and Ethereum mainnet, for createEVMClient. MetaMask Connect adds
|
|
28
|
+
* mainnet to every connect, and a phone wallet with none of the app's chains
|
|
29
|
+
* enabled grants only mainnet. The session then sits on chain 1, and MetaMask
|
|
30
|
+
* Connect refuses every request on a chain it was not given, personal_sign
|
|
31
|
+
* included, so the wallet could never prove itself (PROMA-121).
|
|
32
|
+
*/
|
|
33
|
+
export declare function metaMaskSupportedNetworks(networks: Record<Hex, string>, transports?: ConnectorConfig["transports"]): Record<Hex, string>;
|
|
26
34
|
/** The chain ids a connect asks MetaMask for, the requested one first (it becomes the active chain). */
|
|
27
35
|
export declare function metaMaskConnectChainIds(networks: Record<Hex, string>, chainId?: number): Hex[];
|
|
28
36
|
/** Test seam: forget the page's client so the next connector builds a fresh one. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metaMaskConnectConnector.d.ts","sourceRoot":"","sources":["../../../src/connectkit/connectors/metaMaskConnectConnector.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAA2B,KAAK,iBAAiB,EAAmB,MAAM,OAAO,CAAC;AACzF,OAAO,EAEL,KAAK,KAAK,EAEV,KAAK,GAAG,EAKT,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"metaMaskConnectConnector.d.ts","sourceRoot":"","sources":["../../../src/connectkit/connectors/metaMaskConnectConnector.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAA2B,KAAK,iBAAiB,EAAmB,MAAM,OAAO,CAAC;AACzF,OAAO,EAEL,KAAK,KAAK,EAEV,KAAK,GAAG,EAKT,MAAM,MAAM,CAAC;AAKd,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,6EAA6E;IAC7E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,KAAK,eAAe,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAGxD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,GACzC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAQrB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,EAC7B,UAAU,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,GACzC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAOrB;AAwED,wGAAwG;AACxG,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAK9F;AAoED,oFAAoF;AACpF,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD;AAWD,wBAAgB,eAAe,CAAC,UAAU,EAAE,yBAAyB,wFAmHpE;yBAnHe,eAAe"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CreateConnectorFn } from "wagmi";
|
|
2
|
-
import {
|
|
2
|
+
import type { CoinbaseWalletParameters } from "wagmi/connectors/coinbaseWallet";
|
|
3
3
|
import type { EthereumProviderOptions as AaveAccountOptions } from "@aave/account";
|
|
4
4
|
type DefaultConnectorsProps = {
|
|
5
5
|
app: {
|