From 800ce935bedfa69a29ae957094e674f0936fd245 Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 11 Jan 2023 14:56:23 +1100 Subject: [PATCH 01/13] Working display all wallets --- packages/core/src/views/connect/Index.svelte | 1 + packages/demo/src/App.svelte | 7 +- packages/injected/src/helpers.ts | 13 +-- packages/injected/src/index.ts | 111 ++++++++++++------- packages/injected/src/types.ts | 26 +++-- packages/injected/src/validation.ts | 7 +- 6 files changed, 101 insertions(+), 64 deletions(-) diff --git a/packages/core/src/views/connect/Index.svelte b/packages/core/src/views/connect/Index.svelte index abcd2ae90..342e2bbb3 100644 --- a/packages/core/src/views/connect/Index.svelte +++ b/packages/core/src/views/connect/Index.svelte @@ -170,6 +170,7 @@ } catch (error) { const { message } = error as { message: string } connectingErrorMessage = message + connectingWalletLabel = '' scrollToTop() } } diff --git a/packages/demo/src/App.svelte b/packages/demo/src/App.svelte index 9d2b699d4..0cd389bee 100644 --- a/packages/demo/src/App.svelte +++ b/packages/demo/src/App.svelte @@ -70,7 +70,8 @@ ], filter: { // mapping of wallet label to filter here - } + }, + displayUnavailable: true }) const coinbaseWallet = coinbaseModule() @@ -262,7 +263,7 @@ }, position: 'topRight' } - }, + } // containerElements: { // El must be present at time of JS script execution // See ../public/index.html for element example @@ -297,8 +298,6 @@ let toAddress const sendTransaction = async provider => { - await onboard.setChain({ chainId: '0x5' }) - const ethersProvider = new ethers.providers.Web3Provider(provider, 'any') const signer = ethersProvider.getSigner() diff --git a/packages/injected/src/helpers.ts b/packages/injected/src/helpers.ts index fd5ced9a7..40553a1a8 100644 --- a/packages/injected/src/helpers.ts +++ b/packages/injected/src/helpers.ts @@ -1,5 +1,5 @@ -import { ProviderRpcErrorCode, WalletModule } from '@web3-onboard/common' -import { ProviderLabel } from './types.js' +import type { ProviderRpcErrorCode } from '@web3-onboard/common' +import type { InjectedWalletModule } from './types.js' export class ProviderRpcError extends Error { message: string @@ -14,10 +14,5 @@ export class ProviderRpcError extends Error { } } -export const remove = - ({ detected, metamask }: { detected: boolean; metamask: boolean }) => - ({ label }: Partial) => - !( - (label === ProviderLabel.Detected && detected) || - (label === ProviderLabel.MetaMask && metamask) - ) +export const defaultWalletUnavailableMsg = ({ label }: InjectedWalletModule) => + `Please install or enable ${label} to continue` diff --git a/packages/injected/src/index.ts b/packages/injected/src/index.ts index ed49f32f0..386f64f28 100644 --- a/packages/injected/src/index.ts +++ b/packages/injected/src/index.ts @@ -1,12 +1,15 @@ import uniqBy from 'lodash.uniqby' - import type { WalletInit } from '@web3-onboard/common' -import type { InjectedWalletOptions, CustomWindow } from './types.js' import { ProviderLabel } from './types.js' - import standardWallets from './wallets.js' -import { remove } from './helpers.js' import { validateWalletOptions } from './validation.js' +import { defaultWalletUnavailableMsg } from './helpers' + +import type { + InjectedWalletOptions, + CustomWindow, + InjectedWalletModule +} from './types.js' declare const window: CustomWindow @@ -23,9 +26,15 @@ function injected(options?: InjectedWalletOptions): WalletInit { return helpers => { const { device } = helpers - const { custom = [], filter = {} } = options || {} + const { + custom = [], + filter = {}, + displayUnavailable, + sort, + walletUnavailableMessage + } = options || {} const allWallets = [...custom, ...standardWallets] - const deduped = uniqBy(allWallets, ({ label }) => `${label}`) + const deduped = uniqBy(allWallets, ({ label }) => label) const filteredWallets = deduped.filter(wallet => { const { label, platforms } = wallet @@ -51,51 +60,69 @@ function injected(options?: InjectedWalletOptions): WalletInit { let removeMetaMask = false - const validWallets = filteredWallets.filter( - ({ injectedNamespace, checkProviderIdentity, label }) => { - const provider = window[injectedNamespace] as CustomWindow['ethereum'] - - if (!provider) return - - let walletExists - - if (provider.providers && Array.isArray(provider.providers)) { - walletExists = !!provider.providers.filter(provider => - checkProviderIdentity({ provider, device }) - ).length - } else { - walletExists = checkProviderIdentity({ provider, device }) - } - - if ( - walletExists && - provider.isMetaMask && - !provider.overrideIsMetaMask && - label !== ProviderLabel.MetaMask && - label !== 'Detected Wallet' - ) { - removeMetaMask = true - } - - return walletExists + const validWallets = filteredWallets.map(wallet => { + const { injectedNamespace, checkProviderIdentity, label } = wallet + const provider = window[injectedNamespace] as CustomWindow['ethereum'] + + let walletExists = false + + if (provider && provider.providers && Array.isArray(provider.providers)) { + walletExists = !!provider.providers.filter(provider => + checkProviderIdentity({ provider, device }) + ).length + } else { + walletExists = checkProviderIdentity({ provider, device }) } - ) + + if ( + walletExists && + provider.isMetaMask && + !provider.overrideIsMetaMask && + label !== ProviderLabel.MetaMask && + label !== 'Detected Wallet' + ) { + removeMetaMask = true + } + + return walletExists + ? wallet + : displayUnavailable + ? { + ...wallet, + getInterface: async () => { + throw new Error( + walletUnavailableMessage + ? walletUnavailableMessage(wallet) + : defaultWalletUnavailableMsg(wallet) + ) + } + } + : null + }) if (validWallets.length) { const moreThanOneWallet = validWallets.length > 1 // if more than one wallet, then remove detected wallet - return validWallets - .filter( - remove({ - detected: moreThanOneWallet, - metamask: moreThanOneWallet && removeMetaMask - }) - ) - .map(({ label, getIcon, getInterface }) => ({ + const formattedWallets = validWallets + .filter((wallet): wallet is InjectedWalletModule => { + if (wallet === null) return false + + const { label } = wallet + return !( + (label === ProviderLabel.Detected && moreThanOneWallet) || + (label === ProviderLabel.MetaMask && + moreThanOneWallet && + removeMetaMask) + ) + }) + .map(({ label, getIcon, getInterface }: InjectedWalletModule) => ({ label, getIcon, getInterface })) + .sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0)) + + return sort ? sort(formattedWallets) : formattedWallets } return [] diff --git a/packages/injected/src/types.ts b/packages/injected/src/types.ts index 81dde19ec..830245680 100644 --- a/packages/injected/src/types.ts +++ b/packages/injected/src/types.ts @@ -157,20 +157,30 @@ export type InjectedProvider = ExternalProvider & Record export type WalletFilters = { - // A provider label mapped to a list of excluded platforms - // or a boolean indicating if it should be included. + /**A provider label mapped to a list of excluded platforms + * or a boolean indicating if it should be included. */ [key in ProviderLabel | string]?: Platform[] | boolean } export interface InjectedWalletOptions { - // A list of injected wallets to include that - // are not included by default here: ./packages/injected/ + /**A list of injected wallets to include that + * are not included by default here: ./packages/injected/ */ custom?: InjectedWalletModule[] - // A mapping of a provider label to a list of filtered platforms - // or a boolean indicating if it should be included or not. - // By default all wallets listed in ./packages/injected/ - // are included add them to here to remove them. + /**A mapping of a provider label to a list of filtered platforms + * or a boolean indicating if it should be included or not. + * By default all wallets listed in ./packages/injected/ + * are included add them to here to remove them. */ filter?: WalletFilters + /**Will display wallets to be selected even if they + * are not currently available to the end user. + */ + displayUnavailable?: boolean + /**A function that allows for customizing the message to be displayed if the wallet + * is unavailable + */ + walletUnavailableMessage?: (wallet: WalletModule) => string + /**Function that can be used to sort the order of wallets that are displayed */ + sort?: (wallets: WalletModule[]) => WalletModule[] } export interface InjectedWalletModule extends WalletModule { diff --git a/packages/injected/src/validation.ts b/packages/injected/src/validation.ts index 9add17a50..0ac81e039 100644 --- a/packages/injected/src/validation.ts +++ b/packages/injected/src/validation.ts @@ -12,13 +12,18 @@ const walletModule = Joi.object({ }) const wallets = Joi.array().items(walletModule) + const filter = Joi.object().pattern( /\w+/, Joi.any().allow(Joi.boolean(), Joi.array().items(Joi.string())) ) + const walletOptions = Joi.object({ custom: wallets, - filter + filter, + displayUnavailable: Joi.boolean(), + walletUnavailableMessage: Joi.function(), + sort: Joi.function() }) export const validateWalletOptions = ( From ee123789346b332a512a1356fdf52c9bc699e4b9 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 12 Jan 2023 09:38:46 +1100 Subject: [PATCH 02/13] Variable name changes --- packages/demo/src/App.svelte | 8 +++--- packages/injected/src/helpers.ts | 18 +++++++++++-- packages/injected/src/index.ts | 43 ++++++++++++++++++++++---------- packages/injected/src/types.ts | 2 +- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/packages/demo/src/App.svelte b/packages/demo/src/App.svelte index 0cd389bee..8490dbbec 100644 --- a/packages/demo/src/App.svelte +++ b/packages/demo/src/App.svelte @@ -35,6 +35,7 @@ import blocknativeIcon from './blocknative-icon' import blocknativeLogo from './blocknative-logo' import { onMount } from 'svelte' + import { ProviderLabel } from '@web3-onboard/injected-wallets' let windowWidth @@ -68,10 +69,11 @@ custom: [ // include custom injected wallet modules here ], + displayUnavailable: true, filter: { - // mapping of wallet label to filter here - }, - displayUnavailable: true + [ProviderLabel.Binance]: 'unavailable', + [ProviderLabel.Bitski]: 'unavailable' + } }) const coinbaseWallet = coinbaseModule() diff --git a/packages/injected/src/helpers.ts b/packages/injected/src/helpers.ts index 40553a1a8..6075e1a8a 100644 --- a/packages/injected/src/helpers.ts +++ b/packages/injected/src/helpers.ts @@ -1,5 +1,5 @@ -import type { ProviderRpcErrorCode } from '@web3-onboard/common' -import type { InjectedWalletModule } from './types.js' +import type { Device, ProviderRpcErrorCode } from '@web3-onboard/common' +import type { InjectedProvider, InjectedWalletModule } from './types.js' export class ProviderRpcError extends Error { message: string @@ -16,3 +16,17 @@ export class ProviderRpcError extends Error { export const defaultWalletUnavailableMsg = ({ label }: InjectedWalletModule) => `Please install or enable ${label} to continue` + +export const isWalletAvailable = ( + provider: InjectedProvider, + checkProviderIdentity: InjectedWalletModule['checkProviderIdentity'], + device: Device +): boolean => { + if (provider && provider.providers && Array.isArray(provider.providers)) { + return !!provider.providers.filter(provider => + checkProviderIdentity({ provider, device }) + ).length + } else { + return checkProviderIdentity({ provider, device }) + } +} diff --git a/packages/injected/src/index.ts b/packages/injected/src/index.ts index 386f64f28..3d8f156ca 100644 --- a/packages/injected/src/index.ts +++ b/packages/injected/src/index.ts @@ -3,7 +3,7 @@ import type { WalletInit } from '@web3-onboard/common' import { ProviderLabel } from './types.js' import standardWallets from './wallets.js' import { validateWalletOptions } from './validation.js' -import { defaultWalletUnavailableMsg } from './helpers' +import { defaultWalletUnavailableMsg, isWalletAvailable } from './helpers' import type { InjectedWalletOptions, @@ -42,10 +42,31 @@ function injected(options?: InjectedWalletOptions): WalletInit { const filteredWallet = walletFilters === false - const excludedDevice = + const provider = window[ + wallet.injectedNamespace + ] as CustomWindow['ethereum'] + + const walletAvailable = isWalletAvailable( + provider, + wallet.checkProviderIdentity, + device + ) + + let excludedDevice: boolean = false + + if ( + // platform filters Array.isArray(walletFilters) && (walletFilters.includes(device.type) || walletFilters.includes(device.os.name)) + ) { + excludedDevice = true + } + + // remove if filtered by unavailable + if (walletFilters === 'unavailable' && !walletAvailable) { + excludedDevice = true + } const invalidPlatform = !platforms.includes('all') && @@ -64,18 +85,14 @@ function injected(options?: InjectedWalletOptions): WalletInit { const { injectedNamespace, checkProviderIdentity, label } = wallet const provider = window[injectedNamespace] as CustomWindow['ethereum'] - let walletExists = false - - if (provider && provider.providers && Array.isArray(provider.providers)) { - walletExists = !!provider.providers.filter(provider => - checkProviderIdentity({ provider, device }) - ).length - } else { - walletExists = checkProviderIdentity({ provider, device }) - } + const walletAvailable = isWalletAvailable( + provider, + checkProviderIdentity, + device + ) if ( - walletExists && + walletAvailable && provider.isMetaMask && !provider.overrideIsMetaMask && label !== ProviderLabel.MetaMask && @@ -84,7 +101,7 @@ function injected(options?: InjectedWalletOptions): WalletInit { removeMetaMask = true } - return walletExists + return walletAvailable ? wallet : displayUnavailable ? { diff --git a/packages/injected/src/types.ts b/packages/injected/src/types.ts index 830245680..76de0e518 100644 --- a/packages/injected/src/types.ts +++ b/packages/injected/src/types.ts @@ -159,7 +159,7 @@ export type InjectedProvider = ExternalProvider & export type WalletFilters = { /**A provider label mapped to a list of excluded platforms * or a boolean indicating if it should be included. */ - [key in ProviderLabel | string]?: Platform[] | boolean + [key in ProviderLabel | string]?: Platform[] | boolean | 'unavailable' } export interface InjectedWalletOptions { From 8b9760c0c3ca7ffd4606487a3b509f7d4fe0a4e7 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 12 Jan 2023 14:07:51 +1100 Subject: [PATCH 03/13] Filter out undefined wallet modules --- packages/core/src/store/actions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index 0be1851c5..440359d82 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -393,8 +393,10 @@ export function uniqueWalletsByLabel( ): WalletModule[] { return walletModuleList.filter( (wallet, i) => + wallet && walletModuleList.findIndex( - (innerWallet: WalletModule) => innerWallet.label === wallet.label + (innerWallet: WalletModule) => + innerWallet && innerWallet.label === wallet.label ) === i ) } From 487f21e198923f797b3cc06baf36746bb7053570 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 12 Jan 2023 14:08:10 +1100 Subject: [PATCH 04/13] Add examples of how to use new config options --- packages/demo/src/App.svelte | 40 +++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/demo/src/App.svelte b/packages/demo/src/App.svelte index 8490dbbec..5ae25d257 100644 --- a/packages/demo/src/App.svelte +++ b/packages/demo/src/App.svelte @@ -67,13 +67,39 @@ const injected = injectedModule({ custom: [ - // include custom injected wallet modules here - ], - displayUnavailable: true, - filter: { - [ProviderLabel.Binance]: 'unavailable', - [ProviderLabel.Bitski]: 'unavailable' - } + // include custom (not natively supported) injected wallet modules here + ] + // display all wallets even if they are unavailable + // displayUnavailable: true + // but only show Binance and Bitski wallet if they are available + // filter: { + // [ProviderLabel.Binance]: 'unavailable', + // [ProviderLabel.Bitski]: 'unavailable' + // } + // do a manual sort of injected wallets so that MetaMask and Coinbase are ordered first + // sort: wallets => { + // const metaMask = wallets.find( + // ({ label }) => label === ProviderLabel.MetaMask + // ) + // const coinbase = wallets.find( + // ({ label }) => label === ProviderLabel.Coinbase + // ) + + // return ( + // [ + // metaMask, + // coinbase, + // ...wallets.filter( + // ({ label }) => + // label !== ProviderLabel.MetaMask && + // label !== ProviderLabel.Coinbase + // ) + // ] + // // remove undefined values + // .filter(wallet => wallet) + // ) + // } + // walletUnavailableMessage: wallet => `Oops ${wallet.label} is unavailable!` }) const coinbaseWallet = coinbaseModule() From 611f983a2a158ea4ac1a09a4568e2d3953784ec7 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 12 Jan 2023 14:08:28 +1100 Subject: [PATCH 05/13] Refactor filter and map to be a combined reduce --- packages/injected/src/index.ts | 104 +++++++++++++++++---------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/packages/injected/src/index.ts b/packages/injected/src/index.ts index 3d8f156ca..7901e5ed1 100644 --- a/packages/injected/src/index.ts +++ b/packages/injected/src/index.ts @@ -26,6 +26,7 @@ function injected(options?: InjectedWalletOptions): WalletInit { return helpers => { const { device } = helpers + const { custom = [], filter = {}, @@ -33,29 +34,33 @@ function injected(options?: InjectedWalletOptions): WalletInit { sort, walletUnavailableMessage } = options || {} - const allWallets = [...custom, ...standardWallets] - const deduped = uniqBy(allWallets, ({ label }) => label) - const filteredWallets = deduped.filter(wallet => { - const { label, platforms } = wallet - const walletFilters = filter[label] + // combine custom with standard wallets and dedupe + const allWallets = uniqBy( + [...custom, ...standardWallets], + ({ label }) => label + ) - const filteredWallet = walletFilters === false + let removeMetaMask = false + + const wallets = allWallets.reduce((acc, wallet) => { + const { label, platforms, injectedNamespace, checkProviderIdentity } = + wallet - const provider = window[ - wallet.injectedNamespace - ] as CustomWindow['ethereum'] + const walletFilters = filter[label] + const filteredWallet = walletFilters === false + const provider = window[injectedNamespace] as CustomWindow['ethereum'] const walletAvailable = isWalletAvailable( provider, - wallet.checkProviderIdentity, + checkProviderIdentity, device ) let excludedDevice: boolean = false + // dev specified platform filters if ( - // platform filters Array.isArray(walletFilters) && (walletFilters.includes(device.type) || walletFilters.includes(device.os.name)) @@ -63,34 +68,45 @@ function injected(options?: InjectedWalletOptions): WalletInit { excludedDevice = true } - // remove if filtered by unavailable + // unavailable filter if (walletFilters === 'unavailable' && !walletAvailable) { excludedDevice = true } + // wallet specified platform filters const invalidPlatform = !platforms.includes('all') && !platforms.includes(device.type) && !platforms.includes(device.os.name) const supportedWallet = - !filteredWallet && !excludedDevice && !invalidPlatform - - return supportedWallet - }) - - let removeMetaMask = false - - const validWallets = filteredWallets.map(wallet => { - const { injectedNamespace, checkProviderIdentity, label } = wallet - const provider = window[injectedNamespace] as CustomWindow['ethereum'] - - const walletAvailable = isWalletAvailable( - provider, - checkProviderIdentity, - device - ) + !filteredWallet && + !excludedDevice && + !invalidPlatform && + (walletAvailable || displayUnavailable) + + if (supportedWallet) { + acc.push( + // modify wallet to display error if unavailable but displayUnavailable is set + displayUnavailable && !walletAvailable + ? { + ...wallet, + getInterface: async () => { + throw new Error( + walletUnavailableMessage + ? walletUnavailableMessage(wallet) + : defaultWalletUnavailableMsg(wallet) + ) + } + } + : // otherwise add wallet to list as is + wallet + ) + } + // check to see if we need to remove MetaMask + // in the case that the provider gave us a false positive + // for MM wallet if ( walletAvailable && provider.isMetaMask && @@ -101,29 +117,15 @@ function injected(options?: InjectedWalletOptions): WalletInit { removeMetaMask = true } - return walletAvailable - ? wallet - : displayUnavailable - ? { - ...wallet, - getInterface: async () => { - throw new Error( - walletUnavailableMessage - ? walletUnavailableMessage(wallet) - : defaultWalletUnavailableMsg(wallet) - ) - } - } - : null - }) - - if (validWallets.length) { - const moreThanOneWallet = validWallets.length > 1 - // if more than one wallet, then remove detected wallet - const formattedWallets = validWallets - .filter((wallet): wallet is InjectedWalletModule => { - if (wallet === null) return false + return acc + }, [] as InjectedWalletModule[]) + + if (wallets.length) { + const moreThanOneWallet = wallets.length > 1 + // if more than one wallet, then remove detected wallet + const formattedWallets = wallets + .filter(wallet => { const { label } = wallet return !( (label === ProviderLabel.Detected && moreThanOneWallet) || @@ -132,11 +134,13 @@ function injected(options?: InjectedWalletOptions): WalletInit { removeMetaMask) ) }) + // then map to the WalletModule interface .map(({ label, getIcon, getInterface }: InjectedWalletModule) => ({ label, getIcon, getInterface })) + // default sort by alphabetical .sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0)) return sort ? sort(formattedWallets) : formattedWallets From d4b9dfa308bea10490e3aba9db711e0718941e60 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 12 Jan 2023 14:08:45 +1100 Subject: [PATCH 06/13] Add documentation --- packages/injected/README.md | 120 +++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/packages/injected/README.md b/packages/injected/README.md index 7a7b55b53..7e01fe286 100644 --- a/packages/injected/README.md +++ b/packages/injected/README.md @@ -111,7 +111,7 @@ const equal = { // The property on the window where the injected provider is defined // Example: window.ethereum injectedNamespace: 'ethereum', - // A function that returns a bool indicating whether or not the provider is + // A function that returns a bool indicating whether or not the provider is // of a certain identity. In this case, a unique property on the provider // is used to identify the provider. // In most cases this is in the format: `is`. @@ -139,3 +139,121 @@ const onboard = Onboard({ //... other options }) ``` + +## 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: + +```javascript +const injected = injectedModule({ + displayUnavailable: true +}) +``` + +This will render every injected wallet as regardless of whether it has been detected in the window, happy days. +Then the issue of the order of wallets displayed becomes apparent when you have 21 injected wallets at the top of the wallets list. To solve this, all injected wallets are sorted alphabetically by default and there is an additional `sort` parameter which receives the final list of wallets and then returns the list to be rendered. This allows for example setting MetaMask and Coinbase first and then just the rest alphabetically: + +```javascript +const injected = injectedModule({ + // display all wallets even if they are unavailable + displayUnavailable: true, + // do a manual sort of injected wallets so that MetaMask and Coinbase are ordered first + sort: wallets => { + const metaMask = wallets.find( + ({ label }) => label === ProviderLabel.MetaMask + ) + const coinbase = wallets.find( + ({ label }) => label === ProviderLabel.Coinbase + ) + + return ( + [ + metaMask, + coinbase, + ...wallets.filter( + ({ label }) => + label !== ProviderLabel.MetaMask && label !== ProviderLabel.Coinbase + ) + ] + // remove undefined values + .filter(wallet => wallet) + ) + } +}) +``` + +You may want to display all wallets, but filter out specific wallets based on their availability. For example I may want to display all unavailable wallets except when Binance and Bitski wallet is unavailable, then don't show them, but if they are available, then do show them. To do this, the filters value has been extended to have a new value: `'unavailable'`, as in; remove this wallet if it is unavailable, even though `displayUnavailable` wallets is set: + +```javascript +const injected = injectedModule({ + // display all wallets even if they are unavailable + displayUnavailable: true, + // but only show Binance and Bitski wallet if they are available + filter: { + [ProviderLabel.Binance]: 'unavailable', + [ProviderLabel.Bitski]: 'unavailable' + }, + // do a manual sort of injected wallets so that MetaMask and Coinbase are ordered first + sort: wallets => { + const metaMask = wallets.find( + ({ label }) => label === ProviderLabel.MetaMask + ) + const coinbase = wallets.find( + ({ label }) => label === ProviderLabel.Coinbase + ) + + return ( + [ + metaMask, + coinbase, + ...wallets.filter( + ({ label }) => + label !== ProviderLabel.MetaMask && label !== ProviderLabel.Coinbase + ) + ] + // remove undefined values + .filter(wallet => wallet) + ) + } +}) +``` + +If a wallet is selected, but is not available the default error message is: `Please install or enable ${walletName} to continue`. You may want to customise that message, so there is the `walletUnavailableMessage` parameter which is a function that takes the wallet object that is unavailable and returns a string which is the message to display: + +```javascript +const injected = injectedModule({ + custom: [ + // include custom (not natively supported) injected wallet modules here + ], + // display all wallets even if they are unavailable + displayUnavailable: true, + // but only show Binance and Bitski wallet if they are available + filter: { + [ProviderLabel.Binance]: 'unavailable', + [ProviderLabel.Bitski]: 'unavailable' + }, + // do a manual sort of injected wallets so that MetaMask and Coinbase are ordered first + sort: wallets => { + const metaMask = wallets.find( + ({ label }) => label === ProviderLabel.MetaMask + ) + const coinbase = wallets.find( + ({ label }) => label === ProviderLabel.Coinbase + ) + + return ( + [ + metaMask, + coinbase, + ...wallets.filter( + ({ label }) => + label !== ProviderLabel.MetaMask && label !== ProviderLabel.Coinbase + ) + ] + // remove undefined values + .filter(wallet => wallet) + ) + }, + walletUnavailableMessage: wallet => `Oops ${wallet.label} is unavailable!` +}) +``` From ebbff616bf5640b47dd67ae47dd0e9ce527b3eec Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 12 Jan 2023 14:10:01 +1100 Subject: [PATCH 07/13] Increment versions --- packages/core/package.json | 2 +- packages/injected/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 042dc9493..a112c795f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/core", - "version": "2.13.0", + "version": "2.13.1-alpha.1", "description": "Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardized 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", diff --git a/packages/injected/package.json b/packages/injected/package.json index 1cc39483d..88f3f79c7 100644 --- a/packages/injected/package.json +++ b/packages/injected/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/injected-wallets", - "version": "2.5.0", + "version": "2.5.1-alpha.1", "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", From c3d19746deb9c8d2d2368075eb76f9f206288388 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 12 Jan 2023 14:20:13 +1100 Subject: [PATCH 08/13] Fix import --- packages/demo/src/App.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/demo/src/App.svelte b/packages/demo/src/App.svelte index 5ae25d257..5fd8d5744 100644 --- a/packages/demo/src/App.svelte +++ b/packages/demo/src/App.svelte @@ -2,7 +2,7 @@ import Onboard from '@web3-onboard/core' import fortmaticModule from '@web3-onboard/fortmatic' import gnosisModule from '@web3-onboard/gnosis' - import injectedModule from '@web3-onboard/injected-wallets' + import injectedModule, { ProviderLabel } from '@web3-onboard/injected-wallets' import keepkeyModule from '@web3-onboard/keepkey' import keystoneModule from '@web3-onboard/keystone' import ledgerModule from '@web3-onboard/ledger' @@ -35,7 +35,6 @@ import blocknativeIcon from './blocknative-icon' import blocknativeLogo from './blocknative-logo' import { onMount } from 'svelte' - import { ProviderLabel } from '@web3-onboard/injected-wallets' let windowWidth From 1557aaa167e9cb17e6f18f2426caacd0650ac5dd Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 13 Jan 2023 07:35:25 +1100 Subject: [PATCH 09/13] Make it a minor change rather than a bugfix Co-authored-by: Adam Carpenter --- packages/injected/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/injected/package.json b/packages/injected/package.json index 88f3f79c7..f6ee8a4af 100644 --- a/packages/injected/package.json +++ b/packages/injected/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/injected-wallets", - "version": "2.5.1-alpha.1", + "version": "2.6.0-alpha.1", "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", From a1ec7674662351846eecaf35fdd90c2d9a7f5372 Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 13 Jan 2023 07:36:51 +1100 Subject: [PATCH 10/13] Update core version in react and vue packages --- packages/react/package.json | 2 +- packages/vue/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index f491143c5..44f6f05df 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -62,7 +62,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/core": "^2.13.0", + "@web3-onboard/core": "^2.13.1-alpha.1", "@web3-onboard/common": "^2.2.3", "use-sync-external-store": "1.0.0" }, diff --git a/packages/vue/package.json b/packages/vue/package.json index 369e1d219..a4049936e 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -63,7 +63,7 @@ "@vueuse/core": "^8.4.2", "@vueuse/rxjs": "^8.2.0", "@web3-onboard/common": "^2.2.3", - "@web3-onboard/core": "^2.13.0", + "@web3-onboard/core": "^2.13.1-alpha.1", "vue-demi": "^0.12.4" }, "peerDependencies": { From bef421ecef25311aafd323221ad33424d9072254 Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 13 Jan 2023 08:00:52 +1100 Subject: [PATCH 11/13] Bump package versions --- packages/react/package.json | 2 +- packages/vue/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index 44f6f05df..7ba036879 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/react", - "version": "2.5.4", + "version": "2.5.5-alpha.1", "description": "A collection of React hooks for integrating Web3-Onboard in to React and Next.js projects. 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, 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", diff --git a/packages/vue/package.json b/packages/vue/package.json index a4049936e..17ebf21f1 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/vue", - "version": "2.4.4", + "version": "2.4.5.-alpha.1", "description": "A collection of Vue Composables for integrating Web3-Onboard in to a Vue or Nuxt project. Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardized spec compliant web3 providers for all supported wallets, 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", From f9b925614c88a150c958998f94e14db072cbc595 Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 13 Jan 2023 08:12:50 +1100 Subject: [PATCH 12/13] Increment package versions --- packages/core/package.json | 2 +- packages/react/package.json | 4 ++-- packages/vue/package.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index a112c795f..b10e705b1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/core", - "version": "2.13.1-alpha.1", + "version": "2.13.1-alpha.2", "description": "Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardized 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", diff --git a/packages/react/package.json b/packages/react/package.json index 7ba036879..9914cff86 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/react", - "version": "2.5.5-alpha.1", + "version": "2.5.5-alpha.2", "description": "A collection of React hooks for integrating Web3-Onboard in to React and Next.js projects. 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, 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", @@ -62,7 +62,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/core": "^2.13.1-alpha.1", + "@web3-onboard/core": "^2.13.1-alpha.2", "@web3-onboard/common": "^2.2.3", "use-sync-external-store": "1.0.0" }, diff --git a/packages/vue/package.json b/packages/vue/package.json index e4d9558af..e837f01d9 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/vue", - "version": "2.4.5-alpha.1", + "version": "2.4.5-alpha.2", "description": "A collection of Vue Composables for integrating Web3-Onboard in to a Vue or Nuxt project. Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardized spec compliant web3 providers for all supported wallets, 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", @@ -63,7 +63,7 @@ "@vueuse/core": "^8.4.2", "@vueuse/rxjs": "^8.2.0", "@web3-onboard/common": "^2.2.3", - "@web3-onboard/core": "^2.13.1-alpha.1", + "@web3-onboard/core": "^2.13.1-alpha.2", "vue-demi": "^0.12.4" }, "peerDependencies": { From 6d4eca244619d8b300af89ff07e2130fcacac188 Mon Sep 17 00:00:00 2001 From: Aaron Date: Fri, 13 Jan 2023 08:13:24 +1100 Subject: [PATCH 13/13] Add documentation to new features to docs site --- .../src/routes/docs/[...4]wallets/injected.md | 178 ++++++++++++++---- 1 file changed, 141 insertions(+), 37 deletions(-) diff --git a/docs/src/routes/docs/[...4]wallets/injected.md b/docs/src/routes/docs/[...4]wallets/injected.md index 2fa0d992e..9d02c1a3b 100644 --- a/docs/src/routes/docs/[...4]wallets/injected.md +++ b/docs/src/routes/docs/[...4]wallets/injected.md @@ -162,44 +162,148 @@ const onboard = Onboard({ }) ``` +## 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: + +```javascript +const injected = injectedModule({ + displayUnavailable: true +}) +``` + +This will render every injected wallet as regardless of whether it has been detected in the window, happy days. +Then the issue of the order of wallets displayed becomes apparent when you have 21 injected wallets at the top of the wallets list. To solve this, all injected wallets are sorted alphabetically by default and there is an additional `sort` parameter which receives the final list of wallets and then returns the list to be rendered. This allows for example setting MetaMask and Coinbase first and then just the rest alphabetically: + +```javascript +const injected = injectedModule({ + // display all wallets even if they are unavailable + displayUnavailable: true, + // do a manual sort of injected wallets so that MetaMask and Coinbase are ordered first + sort: (wallets) => { + const metaMask = wallets.find(({ label }) => label === ProviderLabel.MetaMask) + const coinbase = wallets.find(({ label }) => label === ProviderLabel.Coinbase) + + return ( + [ + metaMask, + coinbase, + ...wallets.filter( + ({ label }) => label !== ProviderLabel.MetaMask && label !== ProviderLabel.Coinbase + ) + ] + // remove undefined values + .filter((wallet) => wallet) + ) + } +}) +``` + +You may want to display all wallets, but filter out specific wallets based on their availability. For example I may want to display all unavailable wallets except when Binance and Bitski wallet is unavailable, then don't show them, but if they are available, then do show them. To do this, the filters value has been extended to have a new value: `'unavailable'`, as in; remove this wallet if it is unavailable, even though `displayUnavailable` wallets is set: + +```javascript +const injected = injectedModule({ + // display all wallets even if they are unavailable + displayUnavailable: true, + // but only show Binance and Bitski wallet if they are available + filter: { + [ProviderLabel.Binance]: 'unavailable', + [ProviderLabel.Bitski]: 'unavailable' + }, + // do a manual sort of injected wallets so that MetaMask and Coinbase are ordered first + sort: (wallets) => { + const metaMask = wallets.find(({ label }) => label === ProviderLabel.MetaMask) + const coinbase = wallets.find(({ label }) => label === ProviderLabel.Coinbase) + + return ( + [ + metaMask, + coinbase, + ...wallets.filter( + ({ label }) => label !== ProviderLabel.MetaMask && label !== ProviderLabel.Coinbase + ) + ] + // remove undefined values + .filter((wallet) => wallet) + ) + } +}) +``` + +If a wallet is selected, but is not available the default error message is: `Please install or enable ${walletName} to continue`. You may want to customise that message, so there is the `walletUnavailableMessage` parameter which is a function that takes the wallet object that is unavailable and returns a string which is the message to display: + +```javascript +const injected = injectedModule({ + custom: [ + // include custom (not natively supported) injected wallet modules here + ], + // display all wallets even if they are unavailable + displayUnavailable: true, + // but only show Binance and Bitski wallet if they are available + filter: { + [ProviderLabel.Binance]: 'unavailable', + [ProviderLabel.Bitski]: 'unavailable' + }, + // do a manual sort of injected wallets so that MetaMask and Coinbase are ordered first + sort: (wallets) => { + const metaMask = wallets.find(({ label }) => label === ProviderLabel.MetaMask) + const coinbase = wallets.find(({ label }) => label === ProviderLabel.Coinbase) + + return ( + [ + metaMask, + coinbase, + ...wallets.filter( + ({ label }) => label !== ProviderLabel.MetaMask && label !== ProviderLabel.Coinbase + ) + ] + // remove undefined values + .filter((wallet) => wallet) + ) + }, + walletUnavailableMessage: (wallet) => `Oops ${wallet.label} is unavailable!` +}) +``` + ### Injected Wallets Supported Natively -- Metamask - *Desktop & Mobile* (Mobile relies on Wallet Connect and is detected inside MetaMask app browser) -- Binance - *Desktop* -- Coinbase - *Desktop & Mobile* -- Tally - *Desktop* -- Exodus - *Desktop & Mobile* -- Trust - *Mobile* -- Opera - *Desktop & Mobile* -- Status - *Mobile* -- Alphawallet - *Mobile* -- Atoken - *Mobile* -- Bitpie - *Mobile* -- Blockwallet - *Desktop* -- Brave - *Desktop & Mobile* -- D'Cent - *Mobile* -- Frame - *Desktop* -- Huobiwallet - *Mobile* -- Hyperpay - *Mobile* -- IMtoken - *Mobile* -- Liquality - *Desktop* -- Meetone - *Mobile* -- Mykey - *Mobile* -- Ownbit - *Mobile* -- Tokenpocket - *Desktop & Mobile* -- TP - *Mobile* -- xDefi - *Desktop & Mobile* -- 1inch - *Mobile* -- Tokenary - *Mobile* -- GameStop - *Desktop* -- Rabby - *Desktop* -- MathWallet - *Desktop & Mobile* -- Gamestop - *Desktop* -- Bitkeep - *Desktop & Mobile* -- Sequence - *Desktop & Mobile* -- Core - *Desktop* -- Bitski - *Desktop & Mobile* -- Enkrypt - *Desktop & Mobile* +- Metamask - _Desktop & Mobile_ (Mobile relies on Wallet Connect and is detected inside MetaMask app browser) +- Binance - _Desktop_ +- Coinbase - _Desktop & Mobile_ +- Tally - _Desktop_ +- Exodus - _Desktop & Mobile_ +- Trust - _Mobile_ +- Opera - _Desktop & Mobile_ +- Status - _Mobile_ +- Alphawallet - _Mobile_ +- Atoken - _Mobile_ +- Bitpie - _Mobile_ +- Blockwallet - _Desktop_ +- Brave - _Desktop & Mobile_ +- D'Cent - _Mobile_ +- Frame - _Desktop_ +- Huobiwallet - _Mobile_ +- Hyperpay - _Mobile_ +- IMtoken - _Mobile_ +- Liquality - _Desktop_ +- Meetone - _Mobile_ +- Mykey - _Mobile_ +- Ownbit - _Mobile_ +- Tokenpocket - _Desktop & Mobile_ +- TP - _Mobile_ +- xDefi - _Desktop & Mobile_ +- 1inch - _Mobile_ +- Tokenary - _Mobile_ +- GameStop - _Desktop_ +- Rabby - _Desktop_ +- MathWallet - _Desktop & Mobile_ +- Gamestop - _Desktop_ +- Bitkeep - _Desktop & Mobile_ +- Sequence - _Desktop & Mobile_ +- Core - _Desktop_ +- Bitski - _Desktop & Mobile_ +- Enkrypt - _Desktop & Mobile_ ## Build Environments -For build env configurations and setups please see the Build Env section [here](/docs/modules/core#build-environments) \ No newline at end of file + +For build env configurations and setups please see the Build Env section [here](/docs/modules/core#build-environments)