Skip to content

Commit f43e074

Browse files
authoredOct 21, 2024··
feat(connectors): filter mipd by connector rdns (#4343)
* feat(connectors): filter mipd by connector rdns * feat: add connect listeners * test: boost coverage * test(core): eip 6963 announce * test: tweaks * chore: format * chore: changeset * feat: works with ssr flag * chore: changeset * chore: tweaks --------- Co-authored-by: tmm <tmm@users.noreply.github.com>
1 parent 2595ec1 commit f43e074

File tree

9 files changed

+148
-33
lines changed

9 files changed

+148
-33
lines changed
 

‎.changeset/curly-yaks-sin.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@wagmi/connectors": minor
3+
"@wagmi/core": minor
4+
---
5+
6+
Added `rdns` property to connector interface. This is used to filter out duplicate [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) injected providers when [`createConfig#multiInjectedProviderDiscovery`](https://wagmi.sh/core/api/createConfig#multiinjectedproviderdiscovery) is enabled and `createConfig#connectors` already matches EIP-6963 providers' `rdns` property.

‎packages/connectors/src/coinbaseWallet.ts

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ function version4(parameters: Version4Parameters) {
8989
return createConnector<Provider>((config) => ({
9090
id: 'coinbaseWalletSDK',
9191
name: 'Coinbase Wallet',
92+
rdns: 'com.coinbase.wallet',
9293
supportsSimulation: true,
9394
type: coinbaseWallet.type,
9495
async connect({ chainId } = {}) {

‎packages/connectors/src/metaMask.ts

+14-12
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,22 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
9393
return createConnector<Provider, Properties>((config) => ({
9494
id: 'metaMaskSDK',
9595
name: 'MetaMask',
96+
rdns: 'io.metamask',
9697
type: metaMask.type,
9798
async setup() {
9899
const provider = await this.getProvider()
99-
if (provider && !connect) {
100-
connect = this.onConnect.bind(this)
101-
provider.on('connect', connect as Listener)
100+
if (provider?.on) {
101+
if (!connect) {
102+
connect = this.onConnect.bind(this)
103+
provider.on('connect', connect as Listener)
104+
}
105+
106+
// We shouldn't need to listen for `'accountsChanged'` here since the `'connect'` event should suffice (and wallet shouldn't be connected yet).
107+
// Some wallets, like MetaMask, do not implement the `'connect'` event and overload `'accountsChanged'` instead.
108+
if (!accountsChanged) {
109+
accountsChanged = this.onAccountsChanged.bind(this)
110+
provider.on('accountsChanged', accountsChanged as Listener)
111+
}
102112
}
103113
},
104114
async connect({ chainId, isReconnecting } = {}) {
@@ -193,10 +203,6 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
193203
const provider = await this.getProvider()
194204

195205
// Manage EIP-1193 event listeners
196-
if (accountsChanged) {
197-
provider.removeListener('accountsChanged', accountsChanged)
198-
accountsChanged = undefined
199-
}
200206
if (chainChanged) {
201207
provider.removeListener('chainChanged', chainChanged)
202208
chainChanged = undefined
@@ -257,7 +263,7 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
257263
parameters.dappMetadata ??
258264
(typeof window !== 'undefined'
259265
? { url: window.location.origin }
260-
: { name: 'wagmi' }),
266+
: { name: 'wagmi', url: 'https://wagmi.sh' }),
261267
useDeeplink: parameters.useDeeplink ?? true,
262268
})
263269
await sdk.init()
@@ -445,10 +451,6 @@ export function metaMask(parameters: MetaMaskParameters = {}) {
445451
config.emitter.emit('disconnect')
446452

447453
// Manage EIP-1193 event listeners
448-
if (!accountsChanged) {
449-
accountsChanged = this.onAccountsChanged.bind(this)
450-
provider.on('accountsChanged', accountsChanged as Listener)
451-
}
452454
if (chainChanged) {
453455
provider.removeListener('chainChanged', chainChanged)
454456
chainChanged = undefined

‎packages/core/src/connectors/createConnector.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export type CreateConnectorFn<
3737
readonly icon?: string | undefined
3838
readonly id: string
3939
readonly name: string
40+
readonly rdns?: string | undefined
4041
readonly supportsSimulation?: boolean | undefined
4142
readonly type: string
4243

‎packages/core/src/createConfig.test.ts

+83-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { accounts, chain, wait } from '@wagmi/test'
2+
import {
3+
type EIP1193Provider,
4+
type EIP6963ProviderDetail,
5+
announceProvider,
6+
} from 'mipd'
27
import { http } from 'viem'
38
import { expect, test, vi } from 'vitest'
49

510
import { connect } from './actions/connect.js'
611
import { disconnect } from './actions/disconnect.js'
712
import { switchChain } from './actions/switchChain.js'
13+
import { createConnector } from './connectors/createConnector.js'
814
import { mock } from './connectors/mock.js'
915
import { createConfig } from './createConfig.js'
1016
import { createStorage } from './createStorage.js'
@@ -346,10 +352,86 @@ test('behavior: setup connector', async () => {
346352

347353
await connect(config, {
348354
chainId: mainnet.id,
349-
connector: config.connectors[0]!,
355+
connector: config.connectors.find((x) => x.uid === connector.uid)!,
350356
})
351357

352358
expect(config.state.current).toBe(connector.uid)
353359

354360
await disconnect(config)
355361
})
362+
363+
test('behavior: eip 6963 providers', async () => {
364+
const detail_1 = getProviderDetail({ name: 'Foo Wallet', rdns: 'com.foo' })
365+
const detail_2 = getProviderDetail({ name: 'Bar Wallet', rdns: 'com.bar' })
366+
const detail_3 = getProviderDetail({ name: 'Mock', rdns: 'com.mock' })
367+
368+
const config = createConfig({
369+
chains: [mainnet],
370+
connectors: [
371+
createConnector((c) => {
372+
return {
373+
...mock({ accounts })(c),
374+
rdns: 'com.mock',
375+
}
376+
}),
377+
],
378+
transports: {
379+
[mainnet.id]: http(),
380+
},
381+
})
382+
383+
await wait(100)
384+
announceProvider(detail_1)()
385+
await wait(100)
386+
announceProvider(detail_1)()
387+
await wait(100)
388+
announceProvider(detail_2)()
389+
await wait(100)
390+
announceProvider(detail_3)()
391+
await wait(100)
392+
393+
expect(config.connectors.map((x) => x.rdns ?? x.id)).toMatchInlineSnapshot(`
394+
[
395+
"com.mock",
396+
"com.example",
397+
"com.foo",
398+
"com.bar",
399+
]
400+
`)
401+
})
402+
403+
function getProviderDetail(
404+
info: Pick<EIP6963ProviderDetail['info'], 'name' | 'rdns'>,
405+
): EIP6963ProviderDetail {
406+
return {
407+
info: {
408+
icon: 'data:image/svg+xml,<svg width="32px" height="32px" viewBox="0 0 32 32"/>',
409+
uuid: crypto.randomUUID(),
410+
...info,
411+
},
412+
provider: `<EIP1193Provider_${info.rdns}>` as unknown as EIP1193Provider,
413+
}
414+
}
415+
416+
vi.mock(import('mipd'), async (importOriginal) => {
417+
const mod = await importOriginal()
418+
419+
let _cache: typeof mod | undefined
420+
if (!_cache)
421+
_cache = {
422+
...mod,
423+
createStore() {
424+
const store = mod.createStore()
425+
return {
426+
...store,
427+
getProviders() {
428+
return [
429+
getProviderDetail({ name: 'Example', rdns: 'com.example' }),
430+
getProviderDetail({ name: 'Mock', rdns: 'com.mock' }),
431+
]
432+
},
433+
}
434+
},
435+
}
436+
return _cache
437+
})

‎packages/core/src/createConfig.ts

+23-11
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,23 @@ export function createConfig<
9292
: undefined
9393

9494
const chains = createStore(() => rest.chains)
95-
const connectors = createStore(() =>
96-
[
97-
...(rest.connectors ?? []),
98-
...(!ssr
99-
? (mipd?.getProviders().map(providerDetailToConnector) ?? [])
100-
: []),
101-
].map(setup),
102-
)
95+
const connectors = createStore(() => {
96+
const collection = []
97+
const rdnsSet = new Set<string>()
98+
for (const connectorFns of rest.connectors ?? []) {
99+
const connector = setup(connectorFns)
100+
collection.push(connector)
101+
if (!ssr && connector.rdns) rdnsSet.add(connector.rdns)
102+
}
103+
if (!ssr && mipd) {
104+
const providers = mipd.getProviders()
105+
for (const provider of providers) {
106+
if (rdnsSet.has(provider.info.rdns)) continue
107+
collection.push(setup(providerDetailToConnector(provider)))
108+
}
109+
}
110+
return collection
111+
})
103112
function setup(connectorFn: CreateConnectorFn): Connector {
104113
// Set up emitter with uid and add to connector so they are "linked" together.
105114
const emitter = createEmitter<ConnectorEventMap>(uid())
@@ -313,15 +322,18 @@ export function createConfig<
313322

314323
// EIP-6963 subscribe for new wallet providers
315324
mipd?.subscribe((providerDetails) => {
316-
const currentConnectorIds = new Map()
325+
const connectorIdSet = new Set()
326+
const connectorRdnsSet = new Set()
317327
for (const connector of connectors.getState()) {
318-
currentConnectorIds.set(connector.id, true)
328+
connectorIdSet.add(connector.id)
329+
if (connector.rdns) connectorRdnsSet.add(connector.rdns)
319330
}
320331

321332
const newConnectors: Connector[] = []
322333
for (const providerDetail of providerDetails) {
334+
if (connectorRdnsSet.has(providerDetail.info.rdns)) continue
323335
const connector = setup(providerDetailToConnector(providerDetail))
324-
if (currentConnectorIds.has(connector.id)) continue
336+
if (connectorIdSet.has(connector.id)) continue
325337
newConnectors.push(connector)
326338
}
327339

‎packages/core/src/hydrate.ts

+18-8
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,24 @@ export function hydrate(config: Config, parameters: HydrateParameters) {
2323
async onMount() {
2424
if (config._internal.ssr) {
2525
await config._internal.store.persist.rehydrate()
26-
const mipdConnectors = config._internal.mipd
27-
?.getProviders()
28-
.map(config._internal.connectors.providerDetailToConnector)
29-
.map(config._internal.connectors.setup)
30-
config._internal.connectors.setState((connectors) => [
31-
...connectors,
32-
...(mipdConnectors ?? []),
33-
])
26+
if (config._internal.mipd) {
27+
config._internal.connectors.setState((connectors) => {
28+
const rdnsSet = new Set<string>()
29+
for (const connector of connectors ?? []) {
30+
if (connector.rdns) rdnsSet.add(connector.rdns)
31+
}
32+
const mipdConnectors = []
33+
const providers = config._internal.mipd?.getProviders() ?? []
34+
for (const provider of providers) {
35+
if (rdnsSet.has(provider.info.rdns)) continue
36+
const connectorFn =
37+
config._internal.connectors.providerDetailToConnector(provider)
38+
const connector = config._internal.connectors.setup(connectorFn)
39+
mipdConnectors.push(connector)
40+
}
41+
return [...connectors, ...mipdConnectors]
42+
})
43+
}
3444
}
3545

3646
if (reconnectOnMount) reconnect(config)

‎playgrounds/next/src/app/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function Account() {
7878
status: {account.status}
7979
</div>
8080

81-
{account.status !== 'disconnected' && (
81+
{account.status === 'connected' && (
8282
<button type="button" onClick={() => disconnect()}>
8383
Disconnect
8484
</button>

‎site/dev/creating-connectors.md

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ The type error tells you what properties are missing from `createConnector`'s re
5454
- `icon`: Optional icon URL for the connector.
5555
- `id`: The ID for the connector. This should be camel-cased and as short as possible. Example: `fooBarBaz`.
5656
- `name`: Human-readable name for the connector. Example: `'Foo Bar Baz'`.
57+
- `rdns`: Optional reverse DNS for the connector. This is used to filter out duplicate [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) injected providers when `createConfig#multiInjectedProviderDiscovery` is enabled.
5758
- `supportsSimulation`: Whether the connector supports contract simulation. This should be disabled if a connector's wallet cannot accurately simulate contract writes or display contract revert messages. Defaults to `false`.
5859

5960
#### Methods

0 commit comments

Comments
 (0)
Please sign in to comment.