import connectWallet from './connect.js' import disconnectWallet from './disconnect.js' import setChain from './chain.js' import { state } from './store/index.js' import { reset$, wallets$ } from './streams.js' import initI18N from './i18n/index.js' import App from './views/Index.svelte' import type { ConnectModalOptions, InitOptions, Notify, Theme } from './types.js' import { APP_INITIAL_STATE, STORAGE_KEYS } from './constants.js' import { configuration, updateConfiguration } from './configuration.js' import updateBalances from './update-balances.js' import { chainIdToHex, getLocalStore, setLocalStore } from './utils.js' import { preflightNotifications } from './preflight-notifications.js' import { validateInitOptions, validateNotify, validateNotifyOptions } from './validation.js' import { addChains, updateAccountCenter, updateNotify, customNotification, setLocale, setPrimaryWallet, setWalletModules, updateConnectModal, updateTheme, updateAppMetadata } from './store/actions.js' import type { PatchedEIP1193Provider } from '@web3-onboard/transaction-preview' import { getBlocknativeSdk } from './services.js' const API = { connectWallet, disconnectWallet, setChain, state: { get: state.get, select: state.select, actions: { setWalletModules, setLocale, updateNotify, customNotification, preflightNotifications, updateBalances, updateAccountCenter, setPrimaryWallet, updateTheme, updateAppMetadata } } } export type OnboardAPI = typeof API export type { InitOptions, ConnectOptions, DisconnectOptions, WalletState, ConnectedChain, AccountCenter, AppState, CustomNotification, Notification, Notify, UpdateNotification, PreflightNotificationsOptions, Theme } from './types.js' export type { EIP1193Provider } from '@web3-onboard/common' function init(options: InitOptions): OnboardAPI { if (typeof window === 'undefined') return API if (options) { const error = validateInitOptions(options) if (error) { throw error } } const { wallets, chains, appMetadata, i18n, accountCenter, apiKey, notify, gas, connect, containerElements, transactionPreview, theme, disableFontDownload, unstoppableResolution } = options if (containerElements) updateConfiguration({ containerElements }) const { device, svelteInstance } = configuration if (svelteInstance) { // if already initialized, need to cleanup old instance console.warn('Re-initializing Onboard and resetting back to initial state') reset$.next() } initI18N(i18n) addChains(chainIdToHex(chains)) if (typeof connect !== undefined) { updateConnectModal(connect) } // update accountCenter if (typeof accountCenter !== 'undefined') { let accountCenterUpdate const { hideTransactionProtectionBtn, transactionProtectionInfoLink } = accountCenter if (device.type === 'mobile') { accountCenterUpdate = { ...APP_INITIAL_STATE.accountCenter, hideTransactionProtectionBtn, transactionProtectionInfoLink, ...(accountCenter.mobile ? accountCenter.mobile : {}) } } else if (accountCenter.desktop) { accountCenterUpdate = { ...APP_INITIAL_STATE.accountCenter, hideTransactionProtectionBtn, transactionProtectionInfoLink, ...accountCenter.desktop } } updateAccountCenter(accountCenterUpdate) } // update notify if (typeof notify !== 'undefined') { if ('desktop' in notify || 'mobile' in notify) { const error = validateNotifyOptions(notify) if (error) { throw error } if ( (!notify.desktop || (notify.desktop && !notify.desktop.position)) && accountCenter && accountCenter.desktop && accountCenter.desktop.position ) { notify.desktop.position = accountCenter.desktop.position } if ( (!notify.mobile || (notify.mobile && !notify.mobile.position)) && accountCenter && accountCenter.mobile && accountCenter.mobile.position ) { notify.mobile.position = accountCenter.mobile.position } let notifyUpdate: Partial if (device.type === 'mobile' && notify.mobile) { notifyUpdate = { ...APP_INITIAL_STATE.notify, ...notify.mobile } } else if (notify.desktop) { notifyUpdate = { ...APP_INITIAL_STATE.notify, ...notify.desktop } } updateNotify(notifyUpdate) } else { const error = validateNotify(notify as Notify) if (error) { throw error } const notifyUpdate: Partial = { ...APP_INITIAL_STATE.notify, ...notify } updateNotify(notifyUpdate) } } else { const notifyUpdate: Partial = APP_INITIAL_STATE.notify updateNotify(notifyUpdate) } const app = svelteInstance || mountApp(theme, disableFontDownload) updateConfiguration({ svelteInstance: app, apiKey, initialWalletInit: wallets, gas, transactionPreview, unstoppableResolution }) appMetadata && updateAppMetadata(appMetadata) if (apiKey && transactionPreview) { const getBnSDK = async () => { transactionPreview.init({ containerElement: '#w3o-transaction-preview-container', sdk: await getBlocknativeSdk(), apiKey }) wallets$.subscribe(wallets => { wallets.forEach(({ provider }) => { transactionPreview.patchProvider(provider as PatchedEIP1193Provider) }) }) } getBnSDK() } theme && updateTheme(theme) // handle auto connection of last wallet if ( connect && (connect.autoConnectLastWallet || connect.autoConnectAllPreviousWallet) ) { const lastConnectedWallets = getLocalStore( STORAGE_KEYS.LAST_CONNECTED_WALLET ) try { const lastConnectedWalletsParsed = JSON.parse(lastConnectedWallets) if ( lastConnectedWalletsParsed && Array.isArray(lastConnectedWalletsParsed) && lastConnectedWalletsParsed.length ) { connectAllPreviousWallets(lastConnectedWalletsParsed, connect) } } catch (err) { // Handle for legacy single wallet approach // Above try will throw syntax error is local storage is not json if (err instanceof SyntaxError && lastConnectedWallets) { API.connectWallet({ autoSelect: { label: lastConnectedWallets, disableModals: true } }) } } } return API } const fontFamilyExternallyDefined = ( theme: Theme, disableFontDownload: boolean ): boolean => { if (disableFontDownload) return true if ( document.body && (getComputedStyle(document.body).getPropertyValue( '--onboard-font-family-normal' ) || getComputedStyle(document.body).getPropertyValue('--w3o-font-family')) ) return true if (!theme) return false if (typeof theme === 'object' && theme['--w3o-font-family']) return true return false } const importInterFont = async (): Promise => { const { InterVar } = await import('@web3-onboard/common') // Add Fonts to main page const styleEl = document.createElement('style') styleEl.innerHTML = ` ${InterVar} ` document.body.appendChild(styleEl) } const connectAllPreviousWallets = async ( lastConnectedWallets: Array, connect: ConnectModalOptions ): Promise => { const activeWalletsList = [] const parsedWalletList = lastConnectedWallets if (!connect.autoConnectAllPreviousWallet) { API.connectWallet({ autoSelect: { label: parsedWalletList[0], disableModals: true } }) activeWalletsList.push(parsedWalletList[0]) } else { // Loop in reverse to maintain wallet order for (let i = parsedWalletList.length; i--; ) { const walletConnectionPromise = await API.connectWallet({ autoSelect: { label: parsedWalletList[i], disableModals: true } }) // Update localStorage list for available wallets if (walletConnectionPromise.some(r => r.label === parsedWalletList[i])) { activeWalletsList.unshift(parsedWalletList[i]) } } } setLocalStore( STORAGE_KEYS.LAST_CONNECTED_WALLET, JSON.stringify(activeWalletsList) ) } function mountApp(theme: Theme, disableFontDownload: boolean) { class Onboard extends HTMLElement { constructor() { super() } } if (!customElements.get('onboard-v2')) { customElements.define('onboard-v2', Onboard) } if (!fontFamilyExternallyDefined(theme, disableFontDownload)) { importInterFont() } // add to DOM const onboard = document.createElement('onboard-v2') const target = onboard.attachShadow({ mode: 'open' }) onboard.style.all = 'initial' target.innerHTML = ` ` const connectModalContEl = configuration.containerElements.connectModal const containerElementQuery = connectModalContEl || state.get().accountCenter.containerElement || 'body' const containerElement = document.querySelector(containerElementQuery) if (!containerElement) { throw new Error( `Element with query ${containerElementQuery} does not exist.` ) } containerElement.appendChild(onboard) const app = new App({ target }) return app } export default init