idquia
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
477 lines (413 loc) • 12.2 kB
text/typescript
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<Notify>
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<Notify> = {
...APP_INITIAL_STATE.notify,
...notify
}
updateNotify(notifyUpdate)
}
} else {
const notifyUpdate: Partial<Notify> = 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<void> => {
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<string>,
connect: ConnectModalOptions
): Promise<void> => {
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 = `
<style>
:host {
/* COLORS */
--white: white;
--black: black;
--primary-1: #2F80ED;
--primary-100: #eff1fc;
--primary-200: #d0d4f7;
--primary-300: #b1b8f2;
--primary-400: #929bed;
--primary-500: #6370e5;
--primary-600: #454ea0;
--primary-700: #323873;
--gray-100: #ebebed;
--gray-200: #c2c4c9;
--gray-300: #999ca5;
--gray-400: #707481;
--gray-500: #33394b;
--gray-600: #242835;
--gray-700: #1a1d26;
--success-100: #d1fae3;
--success-200: #baf7d5;
--success-300: #a4f4c6;
--success-400: #8df2b8;
--success-500: #5aec99;
--success-600: #18ce66;
--success-700: #129b4d;
--danger-100: #ffe5e6;
--danger-200: #ffcccc;
--danger-300: #ffb3b3;
--danger-400: #ff8080;
--danger-500: #ff4f4f;
--danger-600: #cc0000;
--danger-700: #660000;
--warning-100: #ffefcc;
--warning-200: #ffe7b3;
--warning-300: #ffd780;
--warning-400: #ffc74c;
--warning-500: #ffaf00;
--warning-600: #cc8c00;
--warning-700: #664600;
/* FONTS */
--font-family-normal: var(--w3o-font-family, Inter, sans-serif);
--font-size-1: 3rem;
--font-size-2: 2.25rem;
--font-size-3: 1.5rem;
--font-size-4: 1.25rem;
--font-size-5: 1rem;
--font-size-6: .875rem;
--font-size-7: .75rem;
--font-line-height-1: 24px;
--font-line-height-2: 20px;
--font-line-height-3: 16px;
--font-line-height-4: 12px;
/* SPACING */
--spacing-1: 3rem;
--spacing-2: 2rem;
--spacing-3: 1.5rem;
--spacing-4: 1rem;
--spacing-5: 0.5rem;
--spacing-6: 0.25rem;
--spacing-7: 0.125rem;
/* BORDER RADIUS */
--border-radius-1: 24px;
--border-radius-2: 20px;
--border-radius-3: 16px;
--border-radius-4: 12px;
--border-radius-5: 8px;
/* SHADOWS */
--shadow-0: none;
--shadow-1: 0px 4px 12px rgba(0, 0, 0, 0.1);
--shadow-2: inset 0px -1px 0px rgba(0, 0, 0, 0.1);
--shadow-3: 0px 4px 16px rgba(0, 0, 0, 0.2);
/* MODAL POSITIONING */
--modal-z-index: 10;
--modal-top: unset;
--modal-right: unset;
--modal-bottom: unset;
--modal-left: unset;
/* MODAL STYLES */
--modal-backdrop: rgba(0, 0, 0, 0.6);
}
</style>
`
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