├── .changeset
├── README.md
└── config.json
├── .github
├── actions
│ └── install-dependencies
│ │ └── action.yml
├── dependabot.yml
├── mipd-dark-hug.svg
├── mipd-light-hug.svg
└── workflows
│ ├── on-pull-request.yml
│ ├── on-push-to-main.yml
│ ├── snapshot.yml
│ └── verify.yml
├── .gitignore
├── .vscode
├── extensions.json
├── settings.json
└── workspace.code-workspace
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── playgrounds
├── vite-react
│ ├── index.html
│ ├── package.json
│ ├── src
│ │ ├── main.tsx
│ │ ├── vite-env.d.ts
│ │ └── wallet.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── vite-svelte
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.svelte
│ │ ├── main.ts
│ │ ├── vite-env.d.ts
│ │ └── wallet.ts
│ ├── svelte.config.js
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
└── vite-vue
│ ├── index.html
│ ├── package.json
│ ├── public
│ └── vite.svg
│ ├── src
│ ├── App.vue
│ ├── main.ts
│ ├── vite-env.d.ts
│ └── wallet.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── rome.json
├── scripts
└── prepublishOnly.ts
├── src
├── index.test.ts
├── index.ts
├── register.ts
├── store.test.ts
├── store.ts
├── types.test-d.ts
├── types.ts
├── utils.test.ts
├── utils.ts
└── window.ts
├── tsconfig.base.json
├── tsconfig.build.json
├── tsconfig.json
└── tsconfig.node.json
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@1.6.3/schema.json",
3 | "changelog": ["@changesets/changelog-github", { "repo": "wagmi-dev/mipd" }],
4 | "commit": false,
5 | "access": "public",
6 | "baseBranch": "main",
7 | "updateInternalDependencies": "patch",
8 | "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
9 | "onlyUpdatePeerDependentsWhenOutOfRange": true
10 | },
11 | "ignore": ["vite-react", "vite-svelte", "vite-vue"]
12 | }
13 |
--------------------------------------------------------------------------------
/.github/actions/install-dependencies/action.yml:
--------------------------------------------------------------------------------
1 | name: "Install dependencies"
2 | description: "Prepare repository and all dependencies"
3 |
4 | runs:
5 | using: "composite"
6 | steps:
7 | - name: Set up pnpm
8 | uses: pnpm/action-setup@v2
9 |
10 | - name: Set up node
11 | uses: actions/setup-node@v3
12 | with:
13 | cache: pnpm
14 | node-version: 18
15 |
16 | - name: Install dependencies
17 | shell: bash
18 | run: pnpm install --ignore-scripts
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'github-actions'
4 | directory: '/'
5 | schedule:
6 | interval: 'monthly'
7 |
--------------------------------------------------------------------------------
/.github/mipd-dark-hug.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.github/mipd-light-hug.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.github/workflows/on-pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Pull request
2 | on:
3 | pull_request:
4 | types: [opened, reopened, synchronize, ready_for_review]
5 |
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
8 | cancel-in-progress: true
9 |
10 | jobs:
11 | verify:
12 | name: Verify
13 | uses: ./.github/workflows/verify.yml
14 | secrets: inherit
15 |
16 | size:
17 | name: Size
18 | runs-on: ubuntu-latest
19 | timeout-minutes: 5
20 |
21 | steps:
22 | - name: Clone repository
23 | uses: actions/checkout@v4
24 |
25 | - name: Install dependencies
26 | uses: ./.github/actions/install-dependencies
27 |
28 | - name: Report build size
29 | uses: preactjs/compressed-size-action@v2
30 | with:
31 | repo-token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/on-push-to-main.yml:
--------------------------------------------------------------------------------
1 | name: Main
2 | on:
3 | push:
4 | branches: [main]
5 |
6 | concurrency:
7 | group: ${{ github.workflow }}-${{ github.ref }}
8 | cancel-in-progress: true
9 |
10 | jobs:
11 | verify:
12 | name: Verify
13 | uses: ./.github/workflows/verify.yml
14 | secrets: inherit
15 |
16 | canary:
17 | name: Release canary
18 | needs: verify
19 | runs-on: ubuntu-latest
20 | timeout-minutes: 5
21 |
22 | steps:
23 | - name: Clone repository
24 | uses: actions/checkout@v4
25 |
26 | - name: Install dependencies
27 | uses: ./.github/actions/install-dependencies
28 |
29 | - name: Setup .npmrc file
30 | uses: actions/setup-node@v4
31 | with:
32 | registry-url: 'https://registry.npmjs.org'
33 |
34 | - name: Set version
35 | run: |
36 | npm --no-git-tag-version version minor
37 | npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-canary.$(date +'%Y%m%dT%H%M%S')
38 |
39 | - name: Build
40 | run: pnpm build
41 |
42 | - name: Publish to npm
43 | run: npm publish --tag canary
44 | env:
45 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
46 |
47 | changesets:
48 | name: Changesets
49 | needs: verify
50 | runs-on: ubuntu-latest
51 | timeout-minutes: 5
52 |
53 | steps:
54 | - name: Clone repository
55 | uses: actions/checkout@v4
56 | with:
57 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
58 | fetch-depth: 0
59 |
60 | - name: Install dependencies
61 | uses: ./.github/actions/install-dependencies
62 |
63 | - name: Create version pull request or publish to npm
64 | uses: changesets/action@v1
65 | with:
66 | title: 'chore: version packages'
67 | commit: 'chore: version packages'
68 | publish: pnpm changeset:release
69 | version: pnpm changeset:version
70 | env:
71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Snapshot
2 |
3 | on: workflow_dispatch
4 |
5 | jobs:
6 | snapshot:
7 | name: Publish Snapshot
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Clone repository
11 | uses: actions/checkout@v4
12 |
13 | - name: Install dependencies
14 | uses: ./.github/actions/install-dependencies
15 |
16 | - name: Publish snapshots
17 | uses: seek-oss/changesets-snapshot@v0
18 | with:
19 | pre-publish: pnpm build
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/verify.yml:
--------------------------------------------------------------------------------
1 | name: Verify
2 | on:
3 | workflow_call:
4 |
5 | jobs:
6 | lint:
7 | name: Lint
8 | runs-on: ubuntu-latest
9 | timeout-minutes: 5
10 |
11 | steps:
12 | - name: Clone repository
13 | uses: actions/checkout@v4
14 |
15 | - name: Install dependencies
16 | uses: ./.github/actions/install-dependencies
17 |
18 | - name: Lint code
19 | run: pnpm format && pnpm lint:fix
20 |
21 | - uses: stefanzweifel/git-auto-commit-action@v5
22 | env:
23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 | with:
25 | commit_message: 'chore: format'
26 | commit_user_name: 'github-actions[bot]'
27 | commit_user_email: 'github-actions[bot]@users.noreply.github.com'
28 |
29 | build:
30 | name: Build
31 | runs-on: ubuntu-latest
32 | timeout-minutes: 5
33 |
34 | steps:
35 | - name: Clone repository
36 | uses: actions/checkout@v4
37 |
38 | - name: Install dependencies
39 | uses: ./.github/actions/install-dependencies
40 |
41 | - name: Build
42 | run: pnpm build
43 |
44 | types:
45 | name: Types
46 | runs-on: ubuntu-latest
47 | timeout-minutes: 5
48 |
49 | steps:
50 | - name: Clone repository
51 | uses: actions/checkout@v4
52 |
53 | - name: Install dependencies
54 | uses: ./.github/actions/install-dependencies
55 |
56 | - name: Build
57 | run: pnpm build
58 |
59 | - name: Check types
60 | run: pnpm typecheck
61 |
62 | - name: Test types
63 | run: pnpm test:typecheck
64 |
65 | test:
66 | name: Test
67 | runs-on: ubuntu-latest
68 | timeout-minutes: 5
69 |
70 | steps:
71 | - name: Clone repository
72 | uses: actions/checkout@v4
73 |
74 | - name: Install dependencies
75 | uses: ./.github/actions/install-dependencies
76 |
77 | - name: Run tests
78 | run: pnpm test:cov
79 |
80 | - name: Upload coverage reports to Codecov
81 | uses: codecov/codecov-action@v4
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.local
2 | .DS_Store
3 | .eslintcache
4 | .next
5 | .pnpm-debug.log*
6 | bench
7 | cache
8 | coverage
9 | node_modules
10 | tsconfig*.tsbuildinfo
11 | dist
12 |
13 | # local env files
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 | .envrc
20 |
21 | # @wagmi/cli
22 | generated.ts
23 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "rome.rome",
4 | "orta.vscode-twoslash-queries",
5 | "svelte.svelte-vscode",
6 | "Vue.volar",
7 | "Vue.vscode-typescript-vue-plugin"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "rome.rome",
3 | "editor.formatOnSave": true,
4 | "javascript.validate.enable": true,
5 | "js/ts.implicitProjectConfig.checkJs": true,
6 | "typescript.tsdk": "node_modules/typescript/lib",
7 | "typescript.enablePromptUseWorkspaceTsdk": true,
8 | "editor.codeActionsOnSave": {
9 | "source.organizeImports.rome": true
10 | },
11 | "[json]": {
12 | "editor.defaultFormatter": "rome.rome"
13 | },
14 | "[javascript]": {
15 | "editor.defaultFormatter": "rome.rome"
16 | },
17 | "[javascriptreact]": {
18 | "editor.defaultFormatter": "rome.rome"
19 | },
20 | "[typescript]": {
21 | "editor.defaultFormatter": "rome.rome"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.vscode/workspace.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "name": "mipd",
5 | "path": "../"
6 | },
7 | {
8 | "name": "playgrounds",
9 | "path": "../playgrounds"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # mipd
2 |
3 | ## 0.0.7
4 |
5 | ### Patch Changes
6 |
7 | - [`1f71eef`](https://github.com/wevm/mipd/commit/1f71eef452f3c8d38b4f7485eb76fbe7475d2977) Thanks [@tmm](https://github.com/tmm)! - Bumped Viem version
8 |
9 | ## 0.0.6
10 |
11 | ### Patch Changes
12 |
13 | - [#7](https://github.com/wevm/mipd/pull/7) [`0d6999c`](https://github.com/wevm/mipd/commit/0d6999c375f817eb67228e84faf449eb5f8e3fef) Thanks [@DanielSinclair](https://github.com/DanielSinclair)! - Enforcing `icon` string type to be a RFC-2397 compliant data URI
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-present weth, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | TypeScript Utilities for EIP-6963: Multi Injected Provider Discovery
12 |
13 |
14 | ## Contents
15 |
16 | - [Install](#install)
17 | - [Store](#store)
18 | - [Utilities](#utilities)
19 | - [`requestProviders`](#requestproviders)
20 | - [`announceProvider`](#announceprovider)
21 | - [`window` Type Polyfill](#window-polyfill)
22 | - [Types](#types)
23 | - [Configuration](#configuration)
24 |
25 | ## Install
26 |
27 | ```bash
28 | npm i mipd
29 | ```
30 |
31 | ```bash
32 | pnpm add mipd
33 | ```
34 |
35 | ```bash
36 | yarn add mipd
37 | ```
38 |
39 | ## Store
40 |
41 | The MIPD Store stores the Providers that have been emitted by a Wallet (or other source), and provides a way to subscribe to the store and retrieve the Providers.
42 |
43 | ### Overview
44 |
45 | ```ts
46 | import { createStore } from 'mipd'
47 |
48 | // Set up a MIPD Store, and request Providers.
49 | const store = createStore()
50 |
51 | // Subscribe to the MIPD Store.
52 | store.subscribe(providerDetails => {
53 | console.log(providerDetails)
54 | // => [EIP6963ProviderDetail, EIP6963ProviderDetail, ...]
55 | })
56 |
57 | // Retrieve emitted Providers.
58 | store.getProviders()
59 | // => [EIP6963ProviderDetail, EIP6963ProviderDetail, ...]
60 |
61 | // Find a Provider Detail.
62 | store.findProvider({ rdns: 'com.example' })
63 | // => EIP6963ProviderDetail | undefined
64 |
65 | // Clear the store, including all Providers.
66 | store.clear()
67 |
68 | // Reset the store, and emit an event to request Providers.
69 | store.reset()
70 |
71 | // Destroy the store, and remove all Providers and event listeners.
72 | store.destroy()
73 | ```
74 |
75 | ### Usage
76 |
77 | #### Vanilla JS
78 |
79 | ```tsx
80 | import { createStore } from 'mipd'
81 |
82 | const store = createStore()
83 |
84 | let providers = store.getProviders()
85 | store.subscribe(providerDetails => (providers = providerDetails))
86 | ```
87 |
88 | #### React
89 |
90 | ```tsx
91 | import { useSyncExternalStore } from 'react'
92 | import { createStore } from 'mipd'
93 |
94 | const store = createStore()
95 |
96 | function Example() {
97 | const providers = useSyncExternalStore(store.subscribe, store.getProviders)
98 | // ...
99 | }
100 | ```
101 |
102 | #### Svelte
103 |
104 | ```html
105 |
112 |
113 |
114 | ```
115 |
116 | #### Vue
117 |
118 | ```html
119 |
127 |
128 |
129 | ```
130 |
131 | ### API
132 |
133 | #### createStore()
134 |
135 | Creates a MIPD Store, and emits an event to request Providers from the Wallet(s).
136 |
137 | ```ts
138 | const store = createStore()
139 | ```
140 |
141 | #### store.subscribe(listener, args)
142 |
143 | Subscribes to the MIPD Store, and returns a function to unsubscribe.
144 |
145 | ```ts
146 | const unsubscribe = store.subscribe(providers => {
147 | console.log(providers)
148 | // => [EIP6963ProviderDetail, EIP6963ProviderDetail, ...]
149 | })
150 | ```
151 |
152 | **Definition**
153 |
154 | ```ts
155 | export type Listener = (
156 | // The updated Providers store.
157 | providerDetails: EIP6963ProviderDetail[],
158 | meta?: {
159 | // The Providers that were added to the store.
160 | added?: EIP6963ProviderDetail[]
161 | // The Providers that were removed from the store.
162 | removed?: EIP6963ProviderDetail[]
163 | },
164 | ) => void
165 |
166 | function subscribe(
167 | // The listener function.
168 | listener: Listener,
169 | args?: {
170 | // If `true`, the listener will be called immediately with the stored Providers.
171 | emitImmediately?: boolean
172 | }
173 | ): () => void // Returns an unsubscribe function.
174 | ```
175 |
176 | #### store.getProviders()
177 |
178 | Returns the current Providers.
179 |
180 | ```ts
181 | const providers = store.getProviders()
182 | // => [EIP6963ProviderDetail, EIP6963ProviderDetail, ...]
183 | ```
184 |
185 | **Definition**
186 |
187 | ```ts
188 | function getProviders(): EIP6963ProviderDetail[]
189 | ```
190 |
191 | #### store.findProvider(args)
192 |
193 | Finds a provider detail by its RDNS (Reverse Domain Name Identifier).
194 |
195 | ```ts
196 | const provider = store.findProvider({ rdns: 'com.example' })
197 | ```
198 |
199 | **Definition**
200 |
201 | ```ts
202 | function findProvider(args: {
203 | // The RDNS of the Provider Detail to find.
204 | rdns: string
205 | }): EIP6963ProviderDetail | undefined
206 | ```
207 |
208 | #### store.clear()
209 |
210 | Clears the store, including all Providers.
211 |
212 | ```ts
213 | store.clear()
214 | ```
215 |
216 | **Definition**
217 |
218 | ```ts
219 | function clear(): void
220 | ```
221 |
222 | #### store.reset()
223 |
224 | Resets the store, and emits an event to request Providers from the Wallet(s).
225 |
226 | ```ts
227 | store.reset()
228 | ```
229 |
230 | **Definition**
231 |
232 | ```ts
233 | function reset(): void
234 | ```
235 |
236 | #### store.destroy()
237 |
238 | Destroys the store, and removes all Providers and event listeners.
239 |
240 | ```ts
241 | store.destroy()
242 | ```
243 |
244 | **Definition**
245 |
246 | ```ts
247 | function destroy(): void
248 | ```
249 |
250 | ## Utilities
251 |
252 | ### requestProviders
253 |
254 | The `requestProviders` utility emits an event to request Providers from the Wallet(s). It returns an `unsubscribe` function to clean up event listeners.
255 |
256 | ```ts
257 | import { requestProviders } from 'mipd'
258 |
259 | let providers = []
260 |
261 | const unsubscribe = requestProviders(providerDetail => providers.push(providerDetail))
262 | ```
263 |
264 | **Definition**
265 |
266 | ```ts
267 | function requestProviders(
268 | // The listener.
269 | listener: (providerDetail: EIP6963ProviderDetail) => void
270 | // Unsubscribe function to clean up the listener.
271 | ): () => void
272 | ```
273 |
274 | ### announceProvider
275 |
276 | The `announceProvider` utility emits an event to announce a Provider to the Wallet(s), and also listen for incoming requests. It returns an `unsubscribe` function to clean up event listeners.
277 |
278 | ```ts
279 | import { announceProvider } from 'mipd'
280 |
281 | const unsubscribe = announceProvider({
282 | info: {
283 | icon: 'data:image/svg+xml, ',
284 | name: 'Example',
285 | rdns: 'com.example',
286 | uuid: '00000000-0000-0000-0000-000000000000'
287 | },
288 | provider: new EIP1193Provider()
289 | })
290 | ```
291 |
292 | **Definition**
293 |
294 | ```ts
295 | function requestProviders(
296 | // The EIP-1193 Provider and Provider Info.
297 | detail: EIP6963ProviderDetail
298 | // Unsubscribe function to clean up the listener.
299 | ): () => void
300 | ```
301 |
302 | ## `window` Polyfill
303 |
304 | By importing the `mipd/window` Polyfill, the types on `window.addEventListener` will be inferred to include the `EIP6963AnnounceProviderEvent` and `EIP6963RequestProviderEvent` types.
305 |
306 | ```ts
307 | import 'mipd/window'
308 |
309 | window.addEventListener(
310 | 'eip6963:announceProvider'
311 | // ^? 'eip6963:announceProvider' | 'eip6963:requestProvider' | 'click' | ...
312 |
313 | event => {
314 | // ^? EIP6963AnnounceProviderEvent
315 |
316 | event.type
317 | // ^? 'eip6963:announceProvider'
318 | event.detail
319 | // ^? EIP6963ProviderDetail
320 | event.detail.info
321 | // ^? EIP6963ProviderInfo
322 | event.detail.provider
323 | // ^? EIP1193Provider
324 | }
325 | )
326 |
327 | window.addEventListener(
328 | 'eip6963:requestProvider'
329 | // ^? 'eip6963:announceProvider' | 'eip6963:requestProvider' | 'click' | ...
330 |
331 | event => {
332 | // ^? EIP6963RequestProviderEvent
333 |
334 | event.type
335 | // ^? 'eip6963:requestProvider'
336 | }
337 | )
338 | ```
339 |
340 | ## Types
341 |
342 | ### EIP6963ProviderDetail
343 |
344 | Event detail from `eip6963:announceProvider`.
345 |
346 | #### Import
347 |
348 | ```ts
349 | import { type EIP6963ProviderDetail } from 'mipd'
350 | ```
351 |
352 | #### Definition
353 |
354 | ```ts
355 | export interface EIP6963ProviderDetail {
356 | info: EIP6963ProviderInfo
357 | provider: TProvider
358 | }
359 | ```
360 |
361 | ### EIP6963ProviderInfo
362 |
363 | Metadata of the EIP-1193 Provider.
364 |
365 | #### Import
366 |
367 | ```ts
368 | import { type EIP6963ProviderInfo } from 'mipd'
369 | ```
370 |
371 | #### Definition
372 |
373 | ```ts
374 | export interface EIP6963ProviderInfo {
375 | icon: string
376 | name: string
377 | rdns?: ... | (string & {})
378 | uuid: string
379 | }
380 | ```
381 |
382 | ### EIP6963AnnounceProviderEvent
383 |
384 | Event type to announce an EIP-1193 Provider.
385 |
386 | #### Import
387 |
388 | ```ts
389 | import { type EIP6963AnnounceProviderEvent } from 'mipd'
390 | ```
391 |
392 | #### Definition
393 |
394 | ```ts
395 | export interface EIP6963AnnounceProviderEvent
396 | extends CustomEvent> {
397 | type: 'eip6963:announceProvider'
398 | }
399 | ```
400 |
401 | ### EIP6963RequestProviderEvent
402 |
403 | Event type to request EIP-1193 Providers.
404 |
405 | #### Import
406 |
407 | ```ts
408 | import { type EIP6963RequestProviderEvent } from 'mipd'
409 | ```
410 |
411 | #### Definition
412 |
413 | ```ts
414 | export interface EIP6963RequestProviderEvent extends Event {
415 | type: 'eip6963:requestProvider'
416 | }
417 | ```
418 |
419 | ## Configuration
420 |
421 | In some cases you might want to tune the global types (e.g. the `EIP1193Provider`). To do this, the following configuration options are available:
422 |
423 | | Type | Default | Description |
424 | | ------------------- | -------------------------------------------------------------- | ---------------------- |
425 | | `provider` | `import('viem').EIP1193Provider` | The EIP-1193 Provider. |
426 | | `rdns` | `'com.coinbase' \| 'com.enkrypt' \| 'io.metamask' \| 'io.zerion' \| (string & {})` | Deterministic identifier for the Provider in the form of an rDNS (Reverse Domain Name Notation) |
427 |
428 | Configuration options are customizable using [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html). Extend the `Register` interface either directly in your code or in a `d.ts` file (e.g. `eip6963.d.ts`):
429 |
430 | ```ts
431 | import { type EIP1193Provider } from './eip1193-provider'
432 |
433 | declare module 'mipd' {
434 | interface Register {
435 | provider: EIP1193Provider
436 | }
437 | }
438 | ```
439 |
440 | ## Authors
441 |
442 | - [@jxom](https://github.com/jxom) (jxom.eth, [Twitter](https://twitter.com/jakemoxey))
443 | - [@tmm](https://github.com/tmm) (awkweb.eth, [Twitter](https://twitter.com/awkweb))
444 |
445 | ## License
446 |
447 | [MIT](/LICENSE) License
448 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mipd",
3 | "description": "TypeScript Utilities for EIP-6963",
4 | "version": "0.0.7",
5 | "license": "MIT",
6 | "repository": "wagmi-dev/mipd",
7 | "scripts": {
8 | "build": "pnpm run clean && pnpm run build:cjs && pnpm run build:esm && pnpm run build:types",
9 | "build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./dist/cjs --removeComments --verbatimModuleSyntax false && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'",
10 | "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir ./dist/esm && echo > ./dist/esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'",
11 | "build:types": "tsc --project tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap",
12 | "changeset": "changeset",
13 | "changeset:release": "pnpm build && changeset publish",
14 | "changeset:version": "changeset version && pnpm install --lockfile-only",
15 | "clean": "rimraf dist *.tsbuildinfo",
16 | "dev:react": "pnpm build && pnpm --filter vite-react dev",
17 | "dev:svelte": "pnpm build && pnpm --filter vite-svelte dev",
18 | "dev:vue": "pnpm build && pnpm --filter vite-vue dev",
19 | "format": "rome format . --write",
20 | "lint": "rome check .",
21 | "lint:fix": "pnpm lint --apply",
22 | "preinstall": "npx only-allow pnpm",
23 | "prepare": "npx simple-git-hooks",
24 | "prepublishOnly": "bun scripts/prepublishOnly.ts",
25 | "size": "size-limit",
26 | "test": "vitest dev",
27 | "test:cov": "vitest run --coverage",
28 | "test:typecheck": "vitest typecheck",
29 | "typecheck": "tsc --noEmit"
30 | },
31 | "files": [
32 | "dist",
33 | "!dist/**/*.tsbuildinfo",
34 | "src/**/*.ts",
35 | "!src/**/*.test.ts",
36 | "!src/**/*.test-d.ts"
37 | ],
38 | "sideEffects": false,
39 | "type": "module",
40 | "main": "./dist/cjs/index.js",
41 | "module": "./dist/esm/index.js",
42 | "types": "./dist/types/index.d.ts",
43 | "typings": "./dist/types/index.d.ts",
44 | "exports": {
45 | ".": {
46 | "types": "./dist/types/index.d.ts",
47 | "import": "./dist/esm/index.js",
48 | "default": "./dist/cjs/index.js"
49 | },
50 | "./window": {
51 | "types": "./dist/types/window.d.ts",
52 | "import": "./dist/esm/window.js",
53 | "default": "./dist/cjs/window.js"
54 | },
55 | "./package.json": "./package.json"
56 | },
57 | "typesVersions": {
58 | "*": {
59 | "window": [
60 | "./src/window.d.ts"
61 | ]
62 | }
63 | },
64 | "peerDependencies": {
65 | "typescript": ">=5.0.4"
66 | },
67 | "peerDependenciesMeta": {
68 | "typescript": {
69 | "optional": true
70 | }
71 | },
72 | "devDependencies": {
73 | "@changesets/changelog-github": "^0.4.8",
74 | "@changesets/cli": "^2.26.1",
75 | "@size-limit/preset-small-lib": "^8.2.6",
76 | "@types/fs-extra": "^11.0.1",
77 | "@types/node": "^20.3.1",
78 | "@vitest/coverage-v8": "^0.32.2",
79 | "bun": "^0.5.9",
80 | "fs-extra": "^11.1.1",
81 | "jsdom": "^22.1.0",
82 | "rimraf": "^5.0.1",
83 | "rome": "~12.1.3",
84 | "simple-git-hooks": "^2.8.1",
85 | "size-limit": "^8.2.6",
86 | "typescript": "^5.0.4",
87 | "viem": "^2.9.1",
88 | "vite": "^4.3.9",
89 | "vitest": "^0.32.2"
90 | },
91 | "contributors": [
92 | "jxom.eth ",
93 | "awkweb.eth "
94 | ],
95 | "funding": [
96 | {
97 | "type": "github",
98 | "url": "https://github.com/sponsors/wagmi-dev"
99 | }
100 | ],
101 | "keywords": [
102 | "eth",
103 | "ethereum",
104 | "dapps",
105 | "wallet",
106 | "web3",
107 | "eip",
108 | "6963"
109 | ],
110 | "simple-git-hooks": {
111 | "pre-commit": "pnpm format && pnpm lint:fix"
112 | },
113 | "packageManager": "pnpm@8.3.1",
114 | "size-limit": [
115 | {
116 | "name": "import { createStore } from 'mipd'",
117 | "path": "./dist/esm/index.js",
118 | "limit": "400 B",
119 | "import": "{ createStore }"
120 | },
121 | {
122 | "name": "import { requestProviders } from 'mipd'",
123 | "path": "./dist/esm/index.js",
124 | "limit": "129 B",
125 | "import": "{ requestProviders }"
126 | },
127 | {
128 | "name": "import { announceProvider } from 'mipd'",
129 | "path": "./dist/esm/index.js",
130 | "limit": "132 B",
131 | "import": "{ announceProvider }"
132 | }
133 | ]
134 | }
135 |
--------------------------------------------------------------------------------
/playgrounds/vite-react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/playgrounds/vite-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-react",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "mipd": "workspace:*",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^18.0.37",
18 | "@types/react-dom": "^18.0.11",
19 | "@vitejs/plugin-react": "^4.0.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/playgrounds/vite-react/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { createStore } from 'mipd'
2 | import { useSyncExternalStore } from 'react'
3 | import ReactDOM from 'react-dom/client'
4 |
5 | const store = createStore()
6 |
7 | export default function App() {
8 | const providers = useSyncExternalStore(store.subscribe, store.getProviders)
9 | return {JSON.stringify(providers, null, 2)}
10 | }
11 |
12 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
13 | ,
14 | )
15 |
--------------------------------------------------------------------------------
/playgrounds/vite-react/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/playgrounds/vite-react/src/wallet.ts:
--------------------------------------------------------------------------------
1 | import { EIP1193Provider, announceProvider } from 'mipd'
2 | import 'mipd/window'
3 |
4 | announceProvider({
5 | info: {
6 | icon: 'data:image/svg+xml, ',
7 | name: 'Example Wallet',
8 | rdns: 'org.example',
9 | uuid: '350670db-19fa-4704-a166-e52e178b59d1',
10 | },
11 | provider: '' as unknown as EIP1193Provider,
12 | })
13 |
14 | announceProvider({
15 | info: {
16 | icon: 'data:image/svg+xml, ',
17 | name: 'Foo Wallet',
18 | rdns: 'org.foo',
19 | uuid: '350670db-19fa-4704-a166-e52e178b59d2',
20 | },
21 | provider: '' as unknown as EIP1193Provider,
22 | })
23 |
24 | await new Promise((res) => setTimeout(res, 1000))
25 |
26 | announceProvider({
27 | info: {
28 | icon: 'data:image/svg+xml, ',
29 | name: 'Bar Wallet',
30 | rdns: 'io.bar',
31 | uuid: '350670db-19fa-4704-a166-e52e178b59d3',
32 | },
33 | provider: '' as unknown as EIP1193Provider,
34 | })
35 |
36 | await new Promise((res) => setTimeout(res, 1000))
37 |
38 | announceProvider({
39 | info: {
40 | icon: 'data:image/svg+xml, ',
41 | name: 'Baz Wallet',
42 | rdns: 'com.baz',
43 | uuid: '350670db-19fa-4704-a166-e52e178b59d4',
44 | },
45 | provider: '' as unknown as EIP1193Provider,
46 | })
47 |
--------------------------------------------------------------------------------
/playgrounds/vite-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/playgrounds/vite-react/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/playgrounds/vite-react/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import { defineConfig } from 'vite'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/README.md:
--------------------------------------------------------------------------------
1 | # Svelte + TS + Vite
2 |
3 | This template should help get you started developing with Svelte and TypeScript in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
8 |
9 | ## Need an official Svelte framework?
10 |
11 | Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
12 |
13 | ## Technical considerations
14 |
15 | **Why use this over SvelteKit?**
16 |
17 | - It brings its own routing solution which might not be preferable for some users.
18 | - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
19 |
20 | This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
21 |
22 | Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
23 |
24 | **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
25 |
26 | Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
27 |
28 | **Why include `.vscode/extensions.json`?**
29 |
30 | Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
31 |
32 | **Why enable `allowJs` in the TS template?**
33 |
34 | While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
35 |
36 | **Why is HMR not preserving my local component state?**
37 |
38 | HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
39 |
40 | If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
41 |
42 | ```ts
43 | // store.ts
44 | // An extremely simple external store
45 | import { writable } from 'svelte/store'
46 | export default writable(0)
47 | ```
48 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + Svelte + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-svelte",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "check": "svelte-check --tsconfig ./tsconfig.json"
11 | },
12 | "dependencies": {
13 | "mipd": "workspace:*"
14 | },
15 | "devDependencies": {
16 | "@sveltejs/vite-plugin-svelte": "^2.0.4",
17 | "@tsconfig/svelte": "^4.0.1",
18 | "svelte": "^3.58.0",
19 | "svelte-check": "^3.3.1",
20 | "tslib": "^2.5.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/src/App.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | {JSON.stringify($providers, null, 2)}
9 |
10 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/src/main.ts:
--------------------------------------------------------------------------------
1 | import App from './App.svelte'
2 |
3 | const app = new App({
4 | target: document.getElementById('app'),
5 | })
6 |
7 | export default app
8 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/src/wallet.ts:
--------------------------------------------------------------------------------
1 | import { EIP1193Provider, announceProvider } from 'mipd'
2 | import 'mipd/window'
3 |
4 | announceProvider({
5 | info: {
6 | icon: 'data:image/svg+xml, ',
7 | name: 'Example Wallet',
8 | rdns: 'org.example',
9 | uuid: '350670db-19fa-4704-a166-e52e178b59d1',
10 | },
11 | provider: '' as unknown as EIP1193Provider,
12 | })
13 |
14 | announceProvider({
15 | info: {
16 | icon: 'data:image/svg+xml, ',
17 | name: 'Foo Wallet',
18 | rdns: 'org.foo',
19 | uuid: '350670db-19fa-4704-a166-e52e178b59d2',
20 | },
21 | provider: '' as unknown as EIP1193Provider,
22 | })
23 |
24 | await new Promise((res) => setTimeout(res, 1000))
25 |
26 | announceProvider({
27 | info: {
28 | icon: 'data:image/svg+xml, ',
29 | name: 'Bar Wallet',
30 | rdns: 'io.bar',
31 | uuid: '350670db-19fa-4704-a166-e52e178b59d3',
32 | },
33 | provider: '' as unknown as EIP1193Provider,
34 | })
35 |
36 | await new Promise((res) => setTimeout(res, 1000))
37 |
38 | announceProvider({
39 | info: {
40 | icon: 'data:image/svg+xml, ',
41 | name: 'Baz Wallet',
42 | rdns: 'com.baz',
43 | uuid: '350670db-19fa-4704-a166-e52e178b59d4',
44 | },
45 | provider: '' as unknown as EIP1193Provider,
46 | })
47 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/svelte.config.js:
--------------------------------------------------------------------------------
1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
2 |
3 | export default {
4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
5 | // for more information about preprocessors
6 | preprocess: vitePreprocess(),
7 | }
8 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/svelte/tsconfig.json",
3 | "compilerOptions": {
4 | "target": "ESNext",
5 | "useDefineForClassFields": true,
6 | "module": "ESNext",
7 | "resolveJsonModule": true,
8 | /**
9 | * Typecheck JS in `.svelte` and `.js` files by default.
10 | * Disable checkJs if you'd like to use dynamic types in JS.
11 | * Note that setting allowJs false does not prevent the use
12 | * of JS in `.svelte` files.
13 | */
14 | "allowJs": true,
15 | "checkJs": true,
16 | "isolatedModules": true
17 | },
18 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
19 | "references": [{ "path": "./tsconfig.node.json" }]
20 | }
21 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler"
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/playgrounds/vite-svelte/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { svelte } from '@sveltejs/vite-plugin-svelte'
2 | import { defineConfig } from 'vite'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [svelte()],
7 | })
8 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + Vue + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-vue",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vue-tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "mipd": "workspace:*",
13 | "vue": "^3.2.47"
14 | },
15 | "devDependencies": {
16 | "@vitejs/plugin-vue": "^4.1.0",
17 | "vue-tsc": "^1.4.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/src/App.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 | {{ state.providers }}
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/src/main.ts:
--------------------------------------------------------------------------------
1 | import App from './App.vue'
2 | import { createApp } from 'vue'
3 |
4 | createApp(App).mount('#app')
5 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/src/wallet.ts:
--------------------------------------------------------------------------------
1 | import { EIP1193Provider, announceProvider } from 'mipd'
2 | import 'mipd/window'
3 |
4 | announceProvider({
5 | info: {
6 | icon: 'data:image/svg+xml, ',
7 | name: 'Example Wallet',
8 | rdns: 'org.example',
9 | uuid: '350670db-19fa-4704-a166-e52e178b59d1',
10 | },
11 | provider: '' as unknown as EIP1193Provider,
12 | })
13 |
14 | announceProvider({
15 | info: {
16 | icon: 'data:image/svg+xml, ',
17 | name: 'Foo Wallet',
18 | rdns: 'org.foo',
19 | uuid: '350670db-19fa-4704-a166-e52e178b59d2',
20 | },
21 | provider: '' as unknown as EIP1193Provider,
22 | })
23 |
24 | await new Promise((res) => setTimeout(res, 1000))
25 |
26 | announceProvider({
27 | info: {
28 | icon: 'data:image/svg+xml, ',
29 | name: 'Bar Wallet',
30 | rdns: 'io.bar',
31 | uuid: '350670db-19fa-4704-a166-e52e178b59d3',
32 | },
33 | provider: '' as unknown as EIP1193Provider,
34 | })
35 |
36 | await new Promise((res) => setTimeout(res, 1000))
37 |
38 | announceProvider({
39 | info: {
40 | icon: 'data:image/svg+xml, ',
41 | name: 'Baz Wallet',
42 | rdns: 'com.baz',
43 | uuid: '350670db-19fa-4704-a166-e52e178b59d4',
44 | },
45 | provider: '' as unknown as EIP1193Provider,
46 | })
47 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "preserve",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/playgrounds/vite-vue/vite.config.ts:
--------------------------------------------------------------------------------
1 | import vue from '@vitejs/plugin-vue'
2 | import { defineConfig } from 'vite'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [vue()],
7 | })
8 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | prefer-workspace-packages: true
2 | packages:
3 | - '.'
4 | - 'playgrounds/*'
5 |
--------------------------------------------------------------------------------
/rome.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "ignore": [
4 | "coverage",
5 | "dist",
6 | "tsconfig.json",
7 | "tsconfig.*.json",
8 | "tsconfig.node.json"
9 | ]
10 | },
11 | "formatter": {
12 | "enabled": true,
13 | "formatWithErrors": false,
14 | "indentStyle": "space",
15 | "indentSize": 2,
16 | "lineWidth": 80
17 | },
18 | "linter": {
19 | "enabled": true,
20 | "rules": {
21 | "recommended": true,
22 | "correctness": {
23 | "noUnusedVariables": "error"
24 | },
25 | "performance": {
26 | "noDelete": "off"
27 | },
28 | "style": {
29 | "noNonNullAssertion": "off",
30 | "useShorthandArrayType": "error"
31 | },
32 | "suspicious": {
33 | "noArrayIndexKey": "off",
34 | "noExplicitAny": "off"
35 | }
36 | }
37 | },
38 | "javascript": {
39 | "formatter": {
40 | "quoteStyle": "single",
41 | "trailingComma": "all",
42 | "semicolons": "asNeeded"
43 | }
44 | },
45 | "organizeImports": {
46 | "enabled": true
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/scripts/prepublishOnly.ts:
--------------------------------------------------------------------------------
1 | import { readJsonSync, writeJsonSync } from 'fs-extra'
2 | import path from 'path'
3 |
4 | // Generates a package.json to be published to NPM with only the necessary fields.
5 | const packageJsonPath = path.join(__dirname, '../package.json')
6 | const tmpPackageJson = readJsonSync(packageJsonPath)
7 |
8 | writeJsonSync(`${packageJsonPath}.tmp`, tmpPackageJson, { spaces: 2 })
9 |
10 | const {
11 | ['simple-git-hooks']: _sgh,
12 | ['size-limit']: _sL,
13 | devDependencies: _dD,
14 | packageManager: _pM,
15 | scripts: _s,
16 | // NOTE: We explicitly don't want to publish the `type` field. We create a separate package.json for `dist/cjs` and `dist/esm` that has the type field.
17 | type: _t,
18 | ...rest
19 | } = tmpPackageJson
20 | writeJsonSync(packageJsonPath, rest, { spaces: 2 })
21 |
--------------------------------------------------------------------------------
/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, it } from 'vitest'
2 |
3 | import * as Exports from './index.js'
4 |
5 | it('should expose correct exports', () => {
6 | expect(Object.keys(Exports)).toMatchInlineSnapshot(`
7 | [
8 | "createStore",
9 | "announceProvider",
10 | "requestProviders",
11 | ]
12 | `)
13 | })
14 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export type {
2 | DefaultRegister,
3 | EIP1193Provider,
4 | Rdns,
5 | Register,
6 | ResolvedRegister,
7 | } from './register.js'
8 |
9 | export {
10 | createStore,
11 | type Listener,
12 | type Store,
13 | } from './store.js'
14 |
15 | export type {
16 | EIP6963AnnounceProviderEvent,
17 | EIP6963ProviderDetail,
18 | EIP6963ProviderInfo,
19 | EIP6963RequestProviderEvent,
20 | } from './types.js'
21 |
22 | export {
23 | type AnnounceProviderParameters,
24 | type AnnounceProviderReturnType,
25 | announceProvider,
26 | type RequestProvidersParameters,
27 | type RequestProvidersReturnType,
28 | requestProviders,
29 | } from './utils.js'
30 |
--------------------------------------------------------------------------------
/src/register.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Override `Register` to customize type options
3 | *
4 | * @example
5 | * import { type EIP1193Provider } from './eip1193'
6 | *
7 | * declare module 'mipd' {
8 | * export interface Register {
9 | * provider: EIP1193Provider
10 | * }
11 | * }
12 | */
13 | // rome-ignore lint/suspicious/noEmptyInterface: For extending global type
14 | export interface Register {}
15 |
16 | export type DefaultRegister = {
17 | /** The EIP-1193 Provider. */
18 | provider: import('viem').EIP1193Provider
19 | /** Reverse Domain Name Notation (rDNS) of the Wallet Provider. */
20 | rdns: 'com.coinbase' | 'com.enkrypt' | 'io.metamask' | 'io.zerion'
21 | }
22 |
23 | export type ResolvedRegister = {
24 | /** The EIP-1193 Provider. */
25 | provider: Register extends {
26 | provider: infer provider extends DefaultRegister['provider']
27 | }
28 | ? provider
29 | : DefaultRegister['provider']
30 | /** Reverse Domain Name Notation (rDNS) of the Wallet Provider. */
31 | rdns: Register extends { rdns: infer rdns extends string }
32 | ? rdns
33 | : DefaultRegister['rdns'] | (string & {}) // loose autocomplete
34 | }
35 |
36 | export type EIP1193Provider = ResolvedRegister['provider']
37 | export type Rdns = ResolvedRegister['rdns']
38 |
--------------------------------------------------------------------------------
/src/store.test.ts:
--------------------------------------------------------------------------------
1 | /** @vitest-environment jsdom */
2 | import type { EIP1193Provider } from 'viem'
3 | import { describe, expect, test } from 'vitest'
4 |
5 | import { type Listener, createStore } from './store.js'
6 | import type { EIP6963ProviderDetail } from './types.js'
7 | import { announceProvider } from './utils.js'
8 |
9 | const detail_1 = {
10 | info: {
11 | icon: 'data:image/svg+xml, ',
12 | name: 'Example Wallet',
13 | rdns: 'org.example',
14 | uuid: '350670db-19fa-4704-a166-e52e178b59d2',
15 | },
16 | provider: '' as unknown as EIP1193Provider,
17 | } as const satisfies EIP6963ProviderDetail
18 |
19 | const detail_2 = {
20 | info: {
21 | icon: 'data:image/svg+xml, ',
22 | name: 'Foo Wallet',
23 | rdns: 'org.foo',
24 | uuid: '12345555-19fa-4704-a166-e52e178b59d2',
25 | },
26 | provider: '' as unknown as EIP1193Provider,
27 | } as const satisfies EIP6963ProviderDetail
28 |
29 | describe('createStore', () => {
30 | test('_listeners', () => {
31 | const store = createStore()
32 |
33 | const unsubscribe_1 = store.subscribe(() => {})
34 | expect(store._listeners().size).toBe(1)
35 |
36 | const unsubscribe_2 = store.subscribe(() => {})
37 | store.subscribe(() => {})
38 | expect(store._listeners().size).toBe(3)
39 |
40 | store.subscribe(() => {})
41 | expect(store._listeners().size).toBe(4)
42 |
43 | unsubscribe_1()
44 | expect(store._listeners().size).toBe(3)
45 | unsubscribe_2()
46 | expect(store._listeners().size).toBe(2)
47 |
48 | store.destroy()
49 | expect(store._listeners().size).toBe(0)
50 | })
51 |
52 | test('clear', () => {
53 | const store = createStore()
54 |
55 | store.subscribe(() => {})
56 | store.subscribe(() => {})
57 | announceProvider(detail_1)()
58 | announceProvider(detail_2)()
59 | expect(store.getProviders().length).toBe(2)
60 |
61 | store.clear()
62 | expect(store.getProviders().length).toBe(0)
63 | })
64 |
65 | test('destroy', () => {
66 | const store = createStore()
67 |
68 | // Setting up listeners and announcing some provider details.
69 | store.subscribe(() => {})
70 | store.subscribe(() => {})
71 | store.subscribe(() => {})
72 | store.subscribe(() => {})
73 | announceProvider(detail_1)()
74 | announceProvider(detail_2)()
75 | expect(store.getProviders().length).toBe(2)
76 | expect(store._listeners().size).toBe(4)
77 |
78 | // Destroying should clear listeners and provider details.
79 | store.destroy()
80 | expect(store.getProviders().length).toBe(0)
81 | expect(store._listeners().size).toBe(0)
82 |
83 | announceProvider(detail_1)()
84 | announceProvider(detail_2)()
85 |
86 | expect(store.getProviders().length).toBe(0)
87 | expect(store._listeners().size).toBe(0)
88 |
89 | // Resetting after destroy should work and emit provider details.
90 | store.reset()
91 |
92 | store.subscribe(() => {})
93 | store.subscribe(() => {})
94 | announceProvider(detail_1)()
95 | announceProvider(detail_2)()
96 |
97 | expect(store.getProviders().length).toBe(2)
98 | expect(store._listeners().size).toBe(2)
99 |
100 | store.destroy()
101 | })
102 |
103 | test('find', () => {
104 | const store = createStore()
105 |
106 | announceProvider(detail_1)()
107 | expect(store.findProvider({ rdns: 'org.example' })).toBe(detail_1)
108 | expect(store.findProvider({ rdns: 'org.foo' })).toBe(undefined)
109 |
110 | announceProvider(detail_2)()
111 | expect(store.findProvider({ rdns: 'org.foo' })).toBe(detail_2)
112 |
113 | store.destroy()
114 | })
115 |
116 | test('get', async () => {
117 | const store = createStore()
118 |
119 | announceProvider(detail_1)()
120 | expect(store.getProviders()).toMatchInlineSnapshot(`
121 | [
122 | {
123 | "info": {
124 | "icon": "data:image/svg+xml, ",
125 | "name": "Example Wallet",
126 | "rdns": "org.example",
127 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
128 | },
129 | "provider": "",
130 | },
131 | ]
132 | `)
133 |
134 | await new Promise((resolve) => setTimeout(resolve, 1000))
135 | announceProvider(detail_2)()
136 | expect(store.getProviders()).toMatchInlineSnapshot(`
137 | [
138 | {
139 | "info": {
140 | "icon": "data:image/svg+xml, ",
141 | "name": "Example Wallet",
142 | "rdns": "org.example",
143 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
144 | },
145 | "provider": "",
146 | },
147 | {
148 | "info": {
149 | "icon": "data:image/svg+xml, ",
150 | "name": "Foo Wallet",
151 | "rdns": "org.foo",
152 | "uuid": "12345555-19fa-4704-a166-e52e178b59d2",
153 | },
154 | "provider": "",
155 | },
156 | ]
157 | `)
158 |
159 | // Test duplicate emitted detail
160 | announceProvider(detail_2)()
161 | expect(store.getProviders()).toMatchInlineSnapshot(`
162 | [
163 | {
164 | "info": {
165 | "icon": "data:image/svg+xml, ",
166 | "name": "Example Wallet",
167 | "rdns": "org.example",
168 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
169 | },
170 | "provider": "",
171 | },
172 | {
173 | "info": {
174 | "icon": "data:image/svg+xml, ",
175 | "name": "Foo Wallet",
176 | "rdns": "org.foo",
177 | "uuid": "12345555-19fa-4704-a166-e52e178b59d2",
178 | },
179 | "provider": "",
180 | },
181 | ]
182 | `)
183 |
184 | return store.destroy()
185 | })
186 |
187 | test('reset', () => {
188 | const store = createStore()
189 |
190 | announceProvider(detail_1)()
191 | announceProvider(detail_2)()
192 | expect(store.getProviders().length).toBe(2)
193 |
194 | store.reset()
195 | expect(store.getProviders().length).toBe(0)
196 |
197 | store.destroy()
198 | })
199 |
200 | test('subscribe', async () => {
201 | const store = createStore()
202 |
203 | let results: Parameters[] = []
204 | const unsubscribe = store.subscribe((providerDetails, providerDetail) =>
205 | results.push([providerDetails, providerDetail]),
206 | )
207 |
208 | announceProvider(detail_1)()
209 | await new Promise((resolve) => setTimeout(resolve, 1000))
210 | announceProvider(detail_2)()
211 |
212 | // Test duplicate emitted detail
213 | announceProvider(detail_2)()
214 |
215 | expect(results.length).toBe(2)
216 | expect(results[0]).toMatchInlineSnapshot(`
217 | [
218 | [
219 | {
220 | "info": {
221 | "icon": "data:image/svg+xml, ",
222 | "name": "Example Wallet",
223 | "rdns": "org.example",
224 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
225 | },
226 | "provider": "",
227 | },
228 | ],
229 | {
230 | "added": [
231 | {
232 | "info": {
233 | "icon": "data:image/svg+xml, ",
234 | "name": "Example Wallet",
235 | "rdns": "org.example",
236 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
237 | },
238 | "provider": "",
239 | },
240 | ],
241 | },
242 | ]
243 | `)
244 | expect(results[1]).toMatchInlineSnapshot(`
245 | [
246 | [
247 | {
248 | "info": {
249 | "icon": "data:image/svg+xml, ",
250 | "name": "Example Wallet",
251 | "rdns": "org.example",
252 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
253 | },
254 | "provider": "",
255 | },
256 | {
257 | "info": {
258 | "icon": "data:image/svg+xml, ",
259 | "name": "Foo Wallet",
260 | "rdns": "org.foo",
261 | "uuid": "12345555-19fa-4704-a166-e52e178b59d2",
262 | },
263 | "provider": "",
264 | },
265 | ],
266 | {
267 | "added": [
268 | {
269 | "info": {
270 | "icon": "data:image/svg+xml, ",
271 | "name": "Foo Wallet",
272 | "rdns": "org.foo",
273 | "uuid": "12345555-19fa-4704-a166-e52e178b59d2",
274 | },
275 | "provider": "",
276 | },
277 | ],
278 | },
279 | ]
280 | `)
281 |
282 | results = []
283 |
284 | store.destroy()
285 | expect(results).toMatchInlineSnapshot(`
286 | [
287 | [
288 | [],
289 | {
290 | "removed": [
291 | {
292 | "info": {
293 | "icon": "data:image/svg+xml, ",
294 | "name": "Example Wallet",
295 | "rdns": "org.example",
296 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
297 | },
298 | "provider": "",
299 | },
300 | {
301 | "info": {
302 | "icon": "data:image/svg+xml, ",
303 | "name": "Foo Wallet",
304 | "rdns": "org.foo",
305 | "uuid": "12345555-19fa-4704-a166-e52e178b59d2",
306 | },
307 | "provider": "",
308 | },
309 | ],
310 | },
311 | ],
312 | ]
313 | `)
314 |
315 | results = []
316 |
317 | unsubscribe()
318 |
319 | announceProvider(detail_1)()
320 | announceProvider(detail_2)()
321 |
322 | expect(results).toHaveLength(0)
323 |
324 | store.destroy()
325 | })
326 |
327 | test('subscribe (emitImmediately)', () => {
328 | const store = createStore()
329 |
330 | announceProvider(detail_1)()
331 | announceProvider(detail_2)()
332 |
333 | const results: Parameters[] = []
334 | store.subscribe((providerDetails, providerDetail) =>
335 | results.push([providerDetails, providerDetail]),
336 | )
337 |
338 | expect(results.length).toBe(0)
339 |
340 | store.subscribe(
341 | (providerDetails, providerDetail) =>
342 | results.push([providerDetails, providerDetail]),
343 | { emitImmediately: true },
344 | )
345 |
346 | expect(results.length).toBe(1)
347 | expect(results).toMatchInlineSnapshot(`
348 | [
349 | [
350 | [
351 | {
352 | "info": {
353 | "icon": "data:image/svg+xml, ",
354 | "name": "Example Wallet",
355 | "rdns": "org.example",
356 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
357 | },
358 | "provider": "",
359 | },
360 | {
361 | "info": {
362 | "icon": "data:image/svg+xml, ",
363 | "name": "Foo Wallet",
364 | "rdns": "org.foo",
365 | "uuid": "12345555-19fa-4704-a166-e52e178b59d2",
366 | },
367 | "provider": "",
368 | },
369 | ],
370 | {
371 | "added": [
372 | {
373 | "info": {
374 | "icon": "data:image/svg+xml, ",
375 | "name": "Example Wallet",
376 | "rdns": "org.example",
377 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
378 | },
379 | "provider": "",
380 | },
381 | {
382 | "info": {
383 | "icon": "data:image/svg+xml, ",
384 | "name": "Foo Wallet",
385 | "rdns": "org.foo",
386 | "uuid": "12345555-19fa-4704-a166-e52e178b59d2",
387 | },
388 | "provider": "",
389 | },
390 | ],
391 | },
392 | ],
393 | ]
394 | `)
395 | })
396 | })
397 |
--------------------------------------------------------------------------------
/src/store.ts:
--------------------------------------------------------------------------------
1 | import type { Rdns } from './register.js'
2 | import type { EIP6963ProviderDetail } from './types.js'
3 | import { requestProviders } from './utils.js'
4 |
5 | export type Listener = (
6 | providerDetails: readonly EIP6963ProviderDetail[],
7 | meta?:
8 | | {
9 | added?: readonly EIP6963ProviderDetail[] | undefined
10 | removed?: readonly EIP6963ProviderDetail[] | undefined
11 | }
12 | | undefined,
13 | ) => void
14 |
15 | export type Store = {
16 | /**
17 | * Clears the store, including all provider details.
18 | */
19 | clear(): void
20 | /**
21 | * Destroys the store, including all provider details and listeners.
22 | */
23 | destroy(): void
24 | /**
25 | * Finds a provider detail by its RDNS (Reverse Domain Name Identifier).
26 | */
27 | findProvider(args: { rdns: Rdns }): EIP6963ProviderDetail | undefined
28 | /**
29 | * Returns all provider details that have been emitted.
30 | */
31 | getProviders(): readonly EIP6963ProviderDetail[]
32 | /**
33 | * Resets the store, and emits an event to request provider details.
34 | */
35 | reset(): void
36 | /**
37 | * Subscribes to emitted provider details.
38 | */
39 | subscribe(
40 | listener: Listener,
41 | args?: { emitImmediately?: boolean | undefined } | undefined,
42 | ): () => void
43 |
44 | /**
45 | * @internal
46 | * Current state of listening listeners.
47 | */
48 | _listeners(): Set
49 | }
50 |
51 | export function createStore(): Store {
52 | const listeners: Set = new Set()
53 | let providerDetails: readonly EIP6963ProviderDetail[] = []
54 |
55 | const request = () =>
56 | requestProviders((providerDetail) => {
57 | if (
58 | providerDetails.some(
59 | ({ info }) => info.uuid === providerDetail.info.uuid,
60 | )
61 | )
62 | return
63 |
64 | providerDetails = [...providerDetails, providerDetail]
65 | listeners.forEach((listener) =>
66 | listener(providerDetails, { added: [providerDetail] }),
67 | )
68 | })
69 | let unwatch = request()
70 |
71 | return {
72 | _listeners() {
73 | return listeners
74 | },
75 | clear() {
76 | listeners.forEach((listener) =>
77 | listener([], { removed: [...providerDetails] }),
78 | )
79 | providerDetails = []
80 | },
81 | destroy() {
82 | this.clear()
83 | listeners.clear()
84 | unwatch?.()
85 | },
86 | findProvider({ rdns }) {
87 | return providerDetails.find(
88 | (providerDetail) => providerDetail.info.rdns === rdns,
89 | )
90 | },
91 | getProviders() {
92 | return providerDetails
93 | },
94 | reset() {
95 | this.clear()
96 | unwatch?.()
97 | unwatch = request()
98 | },
99 | subscribe(listener, { emitImmediately } = {}) {
100 | listeners.add(listener)
101 | if (emitImmediately) listener(providerDetails, { added: providerDetails })
102 | return () => listeners.delete(listener)
103 | },
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/types.test-d.ts:
--------------------------------------------------------------------------------
1 | import type { EIP1193Provider } from 'viem'
2 | import { expectTypeOf, test } from 'vitest'
3 |
4 | import type {
5 | EIP6963AnnounceProviderEvent,
6 | EIP6963ProviderDetail,
7 | EIP6963ProviderInfo,
8 | EIP6963RequestProviderEvent,
9 | } from './index.js'
10 |
11 | test('EIP6963ProviderInfo', () => {
12 | expectTypeOf().toEqualTypeOf
13 | expectTypeOf().toEqualTypeOf
14 | expectTypeOf().toEqualTypeOf<
15 | undefined | 'com.enkrypt' | 'io.metamask' | (string & {})
16 | >
17 | expectTypeOf().toEqualTypeOf
18 |
19 | const KnownRdns_1: EIP6963ProviderInfo = {
20 | icon: 'data:image/svg+xml, ',
21 | name: 'MetaMask',
22 | rdns: 'io.metamask',
23 | uuid: '350670db-19fa-4704-a166-e52e178b59d2',
24 | }
25 | KnownRdns_1
26 |
27 | const UnknownRdns_1: EIP6963ProviderInfo = {
28 | icon: 'data:image/svg+xml, ',
29 | name: 'Example Wallet',
30 | rdns: 'org.example',
31 | uuid: '350670db-19fa-4704-a166-e52e178b59d2',
32 | }
33 | UnknownRdns_1
34 | })
35 |
36 | test('EIP6963ProviderDetail', () => {
37 | expectTypeOf()
38 | .toEqualTypeOf
39 | expectTypeOf()
40 | .toEqualTypeOf
41 |
42 | type CustomProvider = { request: () => void }
43 | expectTypeOf['provider']>()
44 | .toEqualTypeOf
45 | })
46 |
47 | test('EIP6963AnnounceProviderEvent', () => {
48 | expectTypeOf().toEqualTypeOf<
49 | EIP6963ProviderDetail
50 | >()
51 | expectTypeOf<
52 | EIP6963AnnounceProviderEvent['type']
53 | >().toEqualTypeOf<'eip6963:announceProvider'>()
54 |
55 | type CustomProvider = { request: () => void }
56 | expectTypeOf<
57 | EIP6963AnnounceProviderEvent['detail']
58 | >().toEqualTypeOf>()
59 | })
60 |
61 | test('EIP6963RequestProviderEvent', () => {
62 | expectTypeOf<
63 | EIP6963RequestProviderEvent['type']
64 | >().toEqualTypeOf<'eip6963:requestProvider'>()
65 | })
66 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { EIP1193Provider, Rdns } from './register.js'
2 |
3 | /**
4 | * Event detail from the `"eip6963:announceProvider"` event.
5 | */
6 | export interface EIP6963ProviderDetail<
7 | TProvider = EIP1193Provider,
8 | TRdns extends string = Rdns,
9 | > {
10 | info: EIP6963ProviderInfo
11 | provider: TProvider
12 | }
13 |
14 | /**
15 | * Metadata of the EIP-1193 Provider.
16 | */
17 | export interface EIP6963ProviderInfo {
18 | icon: `data:image/${string}` // RFC-2397
19 | name: string
20 | rdns: TRdns
21 | uuid: string
22 | }
23 |
24 | /**
25 | * Event type to announce an EIP-1193 Provider.
26 | */
27 | export interface EIP6963AnnounceProviderEvent
28 | extends CustomEvent> {
29 | type: 'eip6963:announceProvider'
30 | }
31 |
32 | /**
33 | * Event type to request EIP-1193 Providers.
34 | */
35 | export interface EIP6963RequestProviderEvent extends Event {
36 | type: 'eip6963:requestProvider'
37 | }
38 |
--------------------------------------------------------------------------------
/src/utils.test.ts:
--------------------------------------------------------------------------------
1 | /** @vitest-environment jsdom */
2 | import type { EIP1193Provider } from 'viem'
3 | import { expect, test } from 'vitest'
4 |
5 | import type { EIP6963ProviderDetail } from './types.js'
6 | import { announceProvider, requestProviders } from './utils.js'
7 |
8 | const detail_1 = {
9 | info: {
10 | icon: 'data:image/svg+xml, ',
11 | name: 'Example Wallet',
12 | rdns: 'org.example',
13 | uuid: '350670db-19fa-4704-a166-e52e178b59d2',
14 | },
15 | provider: '' as unknown as EIP1193Provider,
16 | } as const satisfies EIP6963ProviderDetail
17 |
18 | const detail_2 = {
19 | info: {
20 | icon: 'data:image/svg+xml, ',
21 | name: 'Foo Wallet',
22 | rdns: 'org.foo',
23 | uuid: '12345555-19fa-4704-a166-e52e178b59d2',
24 | },
25 | provider: '' as unknown as EIP1193Provider,
26 | } as const satisfies EIP6963ProviderDetail
27 |
28 | test('requestProviders', async () => {
29 | const results: EIP6963ProviderDetail[] = []
30 | const unsubscribe = requestProviders((providerDetail) => {
31 | results.push(providerDetail)
32 | })
33 |
34 | announceProvider(detail_1)()
35 | await new Promise((resolve) => setTimeout(resolve, 1000))
36 | announceProvider(detail_2)()
37 |
38 | expect(results.length).toBe(2)
39 | expect(results[0]).toMatchInlineSnapshot(`
40 | {
41 | "info": {
42 | "icon": "data:image/svg+xml, ",
43 | "name": "Example Wallet",
44 | "rdns": "org.example",
45 | "uuid": "350670db-19fa-4704-a166-e52e178b59d2",
46 | },
47 | "provider": "",
48 | }
49 | `)
50 | expect(results[1]).toMatchInlineSnapshot(`
51 | {
52 | "info": {
53 | "icon": "data:image/svg+xml, ",
54 | "name": "Foo Wallet",
55 | "rdns": "org.foo",
56 | "uuid": "12345555-19fa-4704-a166-e52e178b59d2",
57 | },
58 | "provider": "",
59 | }
60 | `)
61 |
62 | unsubscribe?.()
63 | })
64 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import type { EIP1193Provider } from './register.js'
2 | import type {
3 | EIP6963AnnounceProviderEvent,
4 | EIP6963ProviderDetail,
5 | } from './types.js'
6 |
7 | ////////////////////////////////////////////////////////////////////////////
8 | // Announce Provider
9 |
10 | export type AnnounceProviderParameters = EIP6963ProviderDetail<
11 | EIP1193Provider,
12 | string
13 | >
14 | export type AnnounceProviderReturnType = () => void
15 |
16 | /**
17 | * Announces an EIP-1193 Provider.
18 | */
19 | export function announceProvider(
20 | detail: AnnounceProviderParameters,
21 | ): AnnounceProviderReturnType {
22 | const event: CustomEvent = new CustomEvent(
23 | 'eip6963:announceProvider',
24 | { detail: Object.freeze(detail) },
25 | )
26 |
27 | window.dispatchEvent(event)
28 |
29 | const handler = () => window.dispatchEvent(event)
30 | window.addEventListener('eip6963:requestProvider', handler)
31 | return () => window.removeEventListener('eip6963:requestProvider', handler)
32 | }
33 |
34 | ////////////////////////////////////////////////////////////////////////////
35 | // Request Providers
36 |
37 | export type RequestProvidersParameters = (
38 | providerDetail: EIP6963ProviderDetail,
39 | ) => void
40 | export type RequestProvidersReturnType = (() => void) | undefined
41 |
42 | /**
43 | * Watches for EIP-1193 Providers to be announced.
44 | */
45 | export function requestProviders(
46 | listener: RequestProvidersParameters,
47 | ): RequestProvidersReturnType {
48 | if (typeof window === 'undefined') return
49 | const handler = (event: EIP6963AnnounceProviderEvent) =>
50 | listener(event.detail)
51 |
52 | window.addEventListener('eip6963:announceProvider', handler)
53 |
54 | window.dispatchEvent(new CustomEvent('eip6963:requestProvider'))
55 |
56 | return () => window.removeEventListener('eip6963:announceProvider', handler)
57 | }
58 |
--------------------------------------------------------------------------------
/src/window.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | EIP6963AnnounceProviderEvent,
3 | EIP6963RequestProviderEvent,
4 | } from './types.js'
5 |
6 | export {}
7 |
8 | declare global {
9 | interface WindowEventMap {
10 | 'eip6963:announceProvider': EIP6963AnnounceProviderEvent
11 | 'eip6963:requestProvider': EIP6963RequestProviderEvent
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | // This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
3 | "include": [],
4 | "compilerOptions": {
5 | // Incremental builds
6 | // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
7 | "incremental": true,
8 |
9 | // Type checking
10 | "strict": true,
11 | "useDefineForClassFields": true, // Not enabled by default in `strict` mode unless we bump `target` to ES2022.
12 | "noFallthroughCasesInSwitch": true, // Not enabled by default in `strict` mode.
13 | "noImplicitReturns": true, // Not enabled by default in `strict` mode.
14 | "useUnknownInCatchVariables": true, // TODO: This would normally be enabled in `strict` mode but would require some adjustments to the codebase.
15 | "noImplicitOverride": true, // Not enabled by default in `strict` mode.
16 | "noUnusedLocals": true, // Not enabled by default in `strict` mode.
17 | "noUnusedParameters": true, // Not enabled by default in `strict` mode.
18 |
19 | // JavaScript support
20 | "allowJs": false,
21 | "checkJs": false,
22 |
23 | // Interop constraints
24 | "esModuleInterop": false,
25 | "allowSyntheticDefaultImports": false,
26 | "forceConsistentCasingInFileNames": true,
27 | "verbatimModuleSyntax": true,
28 | "importHelpers": true, // This is only used for build validation. Since we do not have `tslib` installed, this will fail if we accidentally make use of anything that'd require injection of helpers.
29 |
30 | // Language and environment
31 | "moduleResolution": "NodeNext",
32 | "module": "ESNext",
33 | "target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping.
34 | "lib": [
35 | "ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances.
36 | "DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped.
37 | ],
38 |
39 | // Skip type checking for node modules
40 | "skipLibCheck": true
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files.
3 | "extends": "./tsconfig.base.json",
4 | "include": ["src"],
5 | "exclude": [
6 | "src/**/*.test.ts",
7 | "src/**/*.test-d.ts"
8 | ],
9 | "compilerOptions": {
10 | "sourceMap": true,
11 | "rootDir": "./src"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // This configuration is used for local development and type checking.
3 | "extends": "./tsconfig.base.json",
4 | "include": ["src"],
5 | "exclude": [],
6 | "references": [{ "path": "./tsconfig.node.json" }]
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | // This configuration is used for local development and type checking of configuration and script files that are not part of the build.
3 | "include": ["scripts"],
4 | "compilerOptions": {
5 | "strict": true,
6 | "composite": true,
7 | "module": "ESNext",
8 | "moduleResolution": "Node",
9 | "allowSyntheticDefaultImports": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------