saepenatus
Version:
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, mul
416 lines (337 loc) • 8.47 kB
text/typescript
import type { Chain, WalletInit, WalletModule } from '@web3-onboard/common'
import { nanoid } from 'nanoid'
import { dispatch } from './index.js'
import { configuration } from '../configuration.js'
import { handleThemeChange, returnTheme } from '../themes.js'
import type {
Account,
AddChainsAction,
AddWalletAction,
AccountCenter,
RemoveWalletAction,
ResetStoreAction,
SetWalletModulesAction,
SetLocaleAction,
UpdateAccountAction,
UpdateAccountCenterAction,
UpdateWalletAction,
WalletState,
UpdateNotifyAction,
Notification,
AddNotificationAction,
RemoveNotificationAction,
UpdateAllWalletsAction,
CustomNotification,
UpdateNotification,
CustomNotificationUpdate,
Notify,
ConnectModalOptions,
UpdateConnectModalAction,
Theme
} from '../types.js'
import {
validateAccountCenterUpdate,
validateLocale,
validateNotification,
validateCustomNotification,
validateCustomNotificationUpdate,
validateString,
validateWallet,
validateWalletInit,
validateUpdateBalances,
validateNotify,
validateConnectModalUpdate,
validateUpdateTheme
} from '../validation.js'
import {
ADD_CHAINS,
UPDATE_WALLET,
RESET_STORE,
ADD_WALLET,
REMOVE_WALLET,
UPDATE_ACCOUNT,
UPDATE_ACCOUNT_CENTER,
UPDATE_NOTIFY,
SET_WALLET_MODULES,
SET_LOCALE,
ADD_NOTIFICATION,
REMOVE_NOTIFICATION,
UPDATE_ALL_WALLETS,
UPDATE_CONNECT_MODAL
} from './constants.js'
export function addChains(chains: Chain[]): void {
// chains are validated on init
const action = {
type: ADD_CHAINS,
payload: chains.map(({ namespace = 'evm', id, rpcUrl, ...rest }) => ({
...rest,
namespace,
id: id.toLowerCase(),
rpcUrl: rpcUrl.trim()
}))
}
dispatch(action as AddChainsAction)
}
export function addWallet(wallet: WalletState): void {
const error = validateWallet(wallet)
if (error) {
console.error(error)
throw error
}
const action = {
type: ADD_WALLET,
payload: wallet
}
dispatch(action as AddWalletAction)
}
export function updateWallet(id: string, update: Partial<WalletState>): void {
const error = validateWallet(update)
if (error) {
console.error(error)
throw error
}
const action = {
type: UPDATE_WALLET,
payload: {
id,
...update
}
}
dispatch(action as UpdateWalletAction)
}
export function removeWallet(id: string): void {
const error = validateString(id, 'wallet id')
if (error) {
throw error
}
const action = {
type: REMOVE_WALLET,
payload: {
id
}
}
dispatch(action as RemoveWalletAction)
}
export function setPrimaryWallet(wallet: WalletState, address?: string): void {
const error =
validateWallet(wallet) || (address && validateString(address, 'address'))
if (error) {
throw error
}
// if also setting the primary account
if (address) {
const account = wallet.accounts.find(ac => ac.address === address)
if (account) {
wallet.accounts = [
account,
...wallet.accounts.filter(({ address }) => address !== account.address)
]
}
}
// add wallet will set it to first wallet since it already exists
addWallet(wallet)
}
export function updateAccount(
id: string,
address: string,
update: Partial<Account>
): void {
const action = {
type: UPDATE_ACCOUNT,
payload: {
id,
address,
...update
}
}
dispatch(action as UpdateAccountAction)
}
export function updateAccountCenter(
update: AccountCenter | Partial<AccountCenter>
): void {
const error = validateAccountCenterUpdate(update)
if (error) {
throw error
}
const action = {
type: UPDATE_ACCOUNT_CENTER,
payload: update
}
dispatch(action as UpdateAccountCenterAction)
}
export function updateConnectModal(
update: ConnectModalOptions | Partial<ConnectModalOptions>
): void {
const error = validateConnectModalUpdate(update)
if (error) {
throw error
}
const action = {
type: UPDATE_CONNECT_MODAL,
payload: update
}
dispatch(action as UpdateConnectModalAction)
}
export function updateNotify(update: Partial<Notify>): void {
const error = validateNotify(update)
if (error) {
throw error
}
const action = {
type: UPDATE_NOTIFY,
payload: update
}
dispatch(action as UpdateNotifyAction)
}
export function addNotification(notification: Notification): void {
const error = validateNotification(notification)
if (error) {
throw error
}
const action = {
type: ADD_NOTIFICATION,
payload: notification
}
dispatch(action as AddNotificationAction)
}
export function addCustomNotification(
notification: CustomNotificationUpdate
): void {
const customNotificationError = validateCustomNotificationUpdate(notification)
if (customNotificationError) {
throw customNotificationError
}
const action = {
type: ADD_NOTIFICATION,
payload: notification
}
dispatch(action as AddNotificationAction)
}
export function customNotification(updatedNotification: CustomNotification): {
dismiss: () => void
update: UpdateNotification
} {
const customNotificationError =
validateCustomNotification(updatedNotification)
if (customNotificationError) {
throw customNotificationError
}
const customIdKey = `customNotification-${nanoid()}`
const notification: CustomNotificationUpdate = {
...updatedNotification,
id: customIdKey,
key: customIdKey
}
addCustomNotification(notification)
const dismiss = () => removeNotification(notification.id)
const update = (
notificationUpdate: CustomNotification
): {
dismiss: () => void
update: UpdateNotification
} => {
const customNotificationError =
validateCustomNotification(updatedNotification)
if (customNotificationError) {
throw customNotificationError
}
const notificationAfterUpdate: CustomNotificationUpdate = {
...notificationUpdate,
id: notification.id,
key: notification.key
}
addCustomNotification(notificationAfterUpdate)
return {
dismiss,
update
}
}
addCustomNotification(notification)
return {
dismiss,
update
}
}
export function removeNotification(id: Notification['id']): void {
if (typeof id !== 'string') {
throw new Error('Notification id must be of type string')
}
const action = {
type: REMOVE_NOTIFICATION,
payload: id
}
dispatch(action as RemoveNotificationAction)
}
export function resetStore(): void {
const action = {
type: RESET_STORE
}
dispatch(action as ResetStoreAction)
}
export function setWalletModules(wallets: WalletInit[]): void {
const error = validateWalletInit(wallets)
if (error) {
throw error
}
const modules = initializeWalletModules(wallets)
const dedupedWallets = uniqueWalletsByLabel(modules)
const action = {
type: SET_WALLET_MODULES,
payload: dedupedWallets
}
dispatch(action as SetWalletModulesAction)
}
export function setLocale(locale: string): void {
const error = validateLocale(locale)
if (error) {
throw error
}
const action = {
type: SET_LOCALE,
payload: locale
}
dispatch(action as SetLocaleAction)
}
export function updateAllWallets(wallets: WalletState[]): void {
const error = validateUpdateBalances(wallets)
if (error) {
throw error
}
const action = {
type: UPDATE_ALL_WALLETS,
payload: wallets
}
dispatch(action as UpdateAllWalletsAction)
}
// ==== HELPERS ==== //
export function initializeWalletModules(modules: WalletInit[]): WalletModule[] {
const { device } = configuration
return modules.reduce((acc, walletInit) => {
const initialized = walletInit({ device })
if (initialized) {
// injected wallets is an array of wallets
acc.push(...(Array.isArray(initialized) ? initialized : [initialized]))
}
return acc
}, [] as WalletModule[])
}
export function uniqueWalletsByLabel(
walletModuleList: WalletModule[]
): WalletModule[] {
return walletModuleList.filter(
(wallet, i) =>
wallet &&
walletModuleList.findIndex(
(innerWallet: WalletModule) =>
innerWallet && innerWallet.label === wallet.label
) === i
)
}
export function updateTheme(theme: Theme): void {
const error = validateUpdateTheme(theme)
if (error) {
throw error
}
const themingObj = returnTheme(theme)
themingObj && handleThemeChange(themingObj)
}