Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT - Add 6963 support to injected wallets module #2076

Merged
merged 9 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@web3-onboard/gas": "^2.1.8",
"@web3-onboard/gnosis": "^2.1.10",
"@web3-onboard/infinity-wallet": "^2.0.4",
"@web3-onboard/injected-wallets": "^2.10.9",
"@web3-onboard/injected-wallets": "^2.10.12.alpha.2",
"@web3-onboard/keepkey": "^2.3.7",
"@web3-onboard/keystone": "^2.3.7",
"@web3-onboard/ledger": "^2.5.1",
Expand Down
15 changes: 14 additions & 1 deletion docs/src/routes/docs/[...4]wallets/[...14]injected/+page.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: Injected Wallets

# {$frontmatter.title}

This module lets web3-onboard automatically detect Browser Injected Wallets such as Metamask or Coinbase Wallet. We recommend you install this module to get the most out of your w3o implementation. This module supports [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) and [recognizes many injected wallets natively](#injected-wallets-supported-natively).
This module lets web3-onboard automatically detect Browser Injected Wallets such as Metamask or Coinbase Wallet. We recommend you install this module to get the most out of your w3o implementation. This module supports [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) and [recognizes many injected wallets natively](#injected-wallets-supported-natively) as well as supports [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) and recognizes any wallet that has implemented 6963 support.

Note: Make sure to install the core module before installing other modules to w3o.

Expand Down Expand Up @@ -189,6 +189,19 @@ const onboard = Onboard({
})
```

### This module supports any injected wallet that has implemented support for [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963)

This can be disabled by passing in `disable6963Support` as true within the injected module init object.

```ts
const injected = injectedModule({ disable6963Support: true })

const onboard = Onboard({
wallets: [injected],
...
})
```

## Display Unavailable Wallets

You may want to display injected wallets that are not currently available to the user and you can use the `displayUnavailable` option to do that:
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"confirmed",
"Injected Wallet",
"Crypto",
"Crypto Wallet"
"Crypto Wallet",
"onchain"
],
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"@web3-onboard/gas": "^2.1.7",
"@web3-onboard/gnosis": "^2.2.1",
"@web3-onboard/infinity-wallet": "^2.0.3",
"@web3-onboard/injected-wallets": "^2.10.12-alpha.1",
"@web3-onboard/injected-wallets": "^2.10.12-alpha.2",
"@web3-onboard/keepkey": "^2.3.7",
"@web3-onboard/keystone": "^2.3.7",
"@web3-onboard/ledger": "^2.6.0-alpha.1",
Expand Down
16 changes: 14 additions & 2 deletions packages/demo/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@

const onboard = Onboard({
wallets: [
metamaskSDKWallet,
// metamaskSDKWallet,
injected,
ledger,
trezor,
Expand Down Expand Up @@ -602,6 +602,10 @@
const updateTheme = () => {
onboard.state.actions.updateTheme(selectedTheme)
}

function isSVG(str) {
leightkt marked this conversation as resolved.
Show resolved Hide resolved
return str.includes('<svg')
}
</script>

<style>
Expand Down Expand Up @@ -816,7 +820,15 @@
{#each $wallets$ as { icon, label, accounts, chains, provider, instance }}
<div class="connected-wallet" data-testid="connected-wallet">
<div class="flex-centered" style="width: 10rem;">
<div style="width: 2rem; height: 2rem">{@html icon}</div>
<div style="width: 2rem; height: 2rem">
{#if isSVG(icon)}
<!-- render svg string -->
{@html icon}
{:else}
<!-- load img url -->
<img style="width: 2rem; height: 2rem" src={icon} alt="logo" />
{/if}
</div>
<span data-testid={label}>{label}</span>
</div>

Expand Down
15 changes: 14 additions & 1 deletion packages/injected/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Quickstart

To allow all injected wallets that are supported, don't pass in any options:
To allow all injected wallets that are supported natively by web3-onboard or wallets that have implemented [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) support - don't pass in any options:

```javascript
import Onboard from '@web3-onboard/core'
Expand Down Expand Up @@ -40,6 +40,19 @@ const connectedWallets = await onboard.connectWallet()
console.log(connectedWallets)
```

### This module supports any injected wallet that has implemented support for [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963)

This can be disabled by passing in `disable6963Support` as true within the injected module init object.

```ts
const injected = injectedModule({ disable6963Support: true })

const onboard = Onboard({
wallets: [injected],
...
})
```

### Injected Wallets Supported Natively

- Metamask - _Desktop & Mobile_
Expand Down
6 changes: 4 additions & 2 deletions packages/injected/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@web3-onboard/injected-wallets",
"version": "2.10.12-alpha.1",
"version": "2.10.12-alpha.2",
"description": "Injected wallet module for connecting browser extension and mobile wallets to Web3-Onboard. Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.",
"keywords": [
"Ethereum",
Expand Down Expand Up @@ -34,7 +34,9 @@
"DeFi Wallet",
"Fordefi",
"Coin98 Wallet",
"FoxWallet"
"FoxWallet",
"6963",
"onchain"
],
"repository": {
"type": "git",
Expand Down
31 changes: 31 additions & 0 deletions packages/injected/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,34 @@ export const isWalletAvailable = (
checkProviderIdentity({ provider, device })
)
}

export
function containsExecutableJavaScript(svgString: string): boolean {
if (!svgString) return false
// Regular expression to match <script> tags
const scriptTagRegex = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi

// Regular expression to match event handler attributes (e.g., onclick, onload)
const eventHandlerRegex = /\bon[a-z]+\s*=\s*["']?(?:javascript:)?/gi

// Regular expression to match href or xlink:href attributes containing "javascript:"
const hrefJavaScriptRegex = /\b(href|xlink:href)\s*=\s*["']?javascript:/gi

// Check for <script> tags
if (scriptTagRegex.test(svgString)) {
return true
}

// Check for event handlers
if (eventHandlerRegex.test(svgString)) {
return true
}

// Check for "javascript:" in href or xlink:href
if (hrefJavaScriptRegex.test(svgString)) {
return true
}

// No executable JavaScript found
return false
}
76 changes: 68 additions & 8 deletions packages/injected/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,71 @@
import uniqBy from 'lodash.uniqby'
import type { WalletInit } from '@web3-onboard/common'
import { createEIP1193Provider, type WalletInit } from '@web3-onboard/common'
import { ProviderLabel } from './types.js'
import standardWallets from './wallets.js'
import { validateWalletOptions } from './validation.js'
import { defaultWalletUnavailableMsg, isWalletAvailable } from './helpers.js'
import {
validateEIP6963ProviderDetail,
validateWalletOptions
} from './validation.js'
import {
containsExecutableJavaScript,
defaultWalletUnavailableMsg,
isWalletAvailable
} from './helpers.js'

import type {
InjectedWalletOptions,
CustomWindow,
InjectedWalletModule
InjectedWalletModule,
EIP6963AnnounceProviderEvent,
InjectedProvider
} from './types.js'

declare const window: CustomWindow

export { ProviderIdentityFlag, ProviderLabel } from './types.js'

const providers6963: InjectedWalletModule[] = []
function checkFor6963Providers() {
// Add event listener for 'eip6963:announceProvider' event
console.log('even listener added')
window.addEventListener('eip6963:announceProvider', (event: Event) => {
const eipEvent = event as EIP6963AnnounceProviderEvent
const { detail } = eipEvent
if (!detail) return

if (eipEvent) {
const result = validateEIP6963ProviderDetail(detail)

if (result && result.error) throw result.error
}

const { info, provider } = detail
const { name, icon } = info

if (containsExecutableJavaScript(icon)) {
console.error(
`The icon for injected wallet: ${name} contains executable JavaScript and has been blocked.`
)
return
}

// Push the provider information to the providers6963 array
providers6963.push({
label: name,
getIcon: async () => icon,
getInterface: async () => ({
provider: createEIP1193Provider(provider)
}),
platforms: ['all'],
eip6963Provider: createEIP1193Provider(provider) as InjectedProvider,
checkProviderIdentity: ({ provider }) => !!provider
})
})

// Dispatch a custom event to request the provider information
window.dispatchEvent(new CustomEvent('eip6963:requestProvider'))
}

function injected(options?: InjectedWalletOptions): WalletInit {
if (typeof window === 'undefined') return () => null

Expand All @@ -24,6 +75,8 @@ function injected(options?: InjectedWalletOptions): WalletInit {
if (result && result.error) throw result.error
}

!options?.disable6963Support && checkFor6963Providers()

return helpers => {
const { device } = helpers

Expand All @@ -37,18 +90,25 @@ function injected(options?: InjectedWalletOptions): WalletInit {

// combine custom with standard wallets and dedupe
const allWallets = uniqBy(
[...custom, ...standardWallets],
[...custom, ...standardWallets, ...providers6963],
({ label }) => label
)

const wallets = allWallets.reduce(
(acc: InjectedWalletModule[], wallet: InjectedWalletModule) => {
const { label, platforms, injectedNamespace, checkProviderIdentity } =
wallet
const {
label,
platforms,
injectedNamespace,
checkProviderIdentity,
eip6963Provider
} = wallet

const walletFilters = filter[label]
const filteredWallet = walletFilters === false
const provider = window[injectedNamespace] as CustomWindow['ethereum']
const provider =
eip6963Provider ||
(window[injectedNamespace!] as CustomWindow['ethereum'])

const walletAvailable = isWalletAvailable(
provider,
Expand Down
38 changes: 35 additions & 3 deletions packages/injected/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export enum ProviderIdentityFlag {
SubWallet = 'isSubWallet',
Kayros = 'isKayros',
FoxWallet = 'isFoxWallet',
Lif3Wallet = 'isLif3Wallet',
Lif3Wallet = 'isLif3Wallet'
}

/**
Expand Down Expand Up @@ -155,7 +155,7 @@ export enum ProviderLabel {
SubWallet = 'SubWallet',
Kayros = 'Kayros',
FoxWallet = 'FoxWallet',
Lif3Wallet = 'Lif3 Wallet',
Lif3Wallet = 'Lif3 Wallet'
}

export interface MeetOneProvider extends ExternalProvider {
Expand Down Expand Up @@ -278,15 +278,47 @@ export interface InjectedWalletOptions {
walletUnavailableMessage?: (wallet: WalletModule) => string
/**Function that can be used to sort the order of wallets that are displayed */
sort?: (wallets: WalletModule[]) => WalletModule[]
/** A boolean that can be passed to disable supporting 6963 (https://eips.ethereum.org/EIPS/eip-6963)
* which will display wallets available on the browser
*/
disable6963Support?: boolean
}

export interface InjectedWalletModule extends WalletModule {
injectedNamespace: InjectedNameSpace
injectedNamespace?: InjectedNameSpace
checkProviderIdentity: (helpers: { provider: any; device: Device }) => boolean
platforms: Platform[]
/**
* A Url to link users to a download page for the wallet
* to be shown if not installed or available on the browser
*/
externalUrl?: string
eip6963Provider?: InjectedProvider
}

// Define a class for the "eip6963:requestProvider" event
export class EIP6963RequestProviderEvent extends Event {
constructor() {
super('eip6963:requestProvider')
}
}

// Define an interface for the "eip6963:announceProvider" event
export interface EIP6963AnnounceProviderEvent extends Event {
type: 'eip6963:announceProvider'
detail: EIP6963ProviderDetail
}

// Define an interface for the provider details
export interface EIP6963ProviderDetail {
info: EIP6963ProviderInfo
provider: EIP1193Provider
}

// Define an interface for the provider information
export interface EIP6963ProviderInfo {
uuid: string // Unique identifier of the wallet extension announcement, keep in mind it changes on every request-announcement cycle
name: string // Name of the wallet extension
icon: string // Icon for the wallet extension
rdns: string // Reverse DNS name of the wallet extension
}
21 changes: 19 additions & 2 deletions packages/injected/src/validation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Joi from 'joi'
import { InjectedWalletOptions } from './types.js'
import { EIP6963ProviderDetail, InjectedWalletOptions } from './types.js'
import { validate, type ValidateReturn } from '@web3-onboard/common'

const walletModule = Joi.object({
Expand All @@ -25,9 +25,26 @@ const walletOptions = Joi.object({
displayUnavailable: [Joi.boolean(), Joi.array().items(Joi.string())],
walletUnavailableMessage: Joi.function(),
sort: Joi.function(),
externalUrl: Joi.string()
externalUrl: Joi.string(),
disable6963Support: Joi.boolean()
})

export const validateWalletOptions = (
data: InjectedWalletOptions | Partial<InjectedWalletOptions>
): ValidateReturn => validate(walletOptions, data)

const eip6963ProviderInfo = Joi.object({
uuid: Joi.string().required(),
name: Joi.string().required(),
icon: Joi.string().required(),
rdns: Joi.string().required()
})

const eip6963ProviderDetail = Joi.object({
info: eip6963ProviderInfo.required(),
provider: Joi.object().required()
})

export const validateEIP6963ProviderDetail = (
data: EIP6963ProviderDetail
): ValidateReturn => validate(eip6963ProviderDetail, data)
Loading
Loading