@web3auth/no-modal
Version:
Multi chain wallet aggregator for web3Auth
322 lines (315 loc) • 13.1 kB
JavaScript
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
import { injected, hydrate } from '@wagmi/core';
import { configKey, createConfig, useConfig, useReconnect, useConnectionEffect } from '@wagmi/vue';
import { randomId } from '@web3auth/auth';
import { isAddress, defineChain, webSocket, http, fallback } from 'viem';
import { defineComponent, h, shallowRef, ref, watch, provide } from 'vue';
import { log } from '../../base/loglevel.js';
import { defaultWagmiConfig } from './constants.js';
import { useWeb3Auth } from '../composables/useWeb3Auth.js';
import { useWeb3AuthDisconnect } from '../composables/useWeb3AuthDisconnect.js';
import { CHAIN_NAMESPACES } from '@toruslabs/base-controllers';
import { WalletInitializationError } from '../../base/errors/index.js';
import { WEB3AUTH_CONNECTOR_ID } from '../../base/connector/constants.js';
function getWeb3authConnector(config) {
return config.connectors.find(c => c.id === WEB3AUTH_CONNECTOR_ID);
}
// Helper to create a Web3Auth connector to connect with wagmi
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function createWeb3AuthConnectorForWagmi(provider) {
const baseConnector = injected({
target: {
provider: provider,
id: WEB3AUTH_CONNECTOR_ID,
name: "Web3Auth"
}
});
return config => {
const connector = baseConnector(config);
const baseOnAccountsChanged = connector.onAccountsChanged.bind(connector);
connector.onAccountsChanged = accounts => {
// we need to handle the `accountsChanged` event emitted on the cross-namespace chain switch.
// on evm -> solana, the accountsChanged event is emitted with the solana address, which is not valid for evm.
// that causes the `invalid account address` error in wagmi. So, here, we're filtering out the solana addresses.
if (accounts.length > 0 && !accounts.every(account => typeof account === "string" && isAddress(account))) {
log.warn("onAccountsChanged::accountsChanged event received on non-EVM address");
return;
}
baseOnAccountsChanged(accounts);
};
return connector;
};
}
// Helper to initialize connectors for the given wallets
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setupConnector(provider, config) {
let connector = getWeb3authConnector(config);
if (connector) return connector;
// Create new connector if not already existing
connector = createWeb3AuthConnectorForWagmi(provider);
const result = config._internal.connectors.setup(connector);
config._internal.connectors.setState(current => [...current, result]);
return result;
}
// Helper to connect a wallet and update wagmi state
async function connectWeb3AuthWithWagmi(connector, config) {
var _config$storage, _config$storage2;
await Promise.all([(_config$storage = config.storage) === null || _config$storage === void 0 ? void 0 : _config$storage.removeItem(`${connector.id}.disconnected`), (_config$storage2 = config.storage) === null || _config$storage2 === void 0 ? void 0 : _config$storage2.setItem("recentConnectorId", connector.id)]);
let chainId = await connector.getChainId();
if (!config.chains.find(c => c.id === chainId)) {
chainId = config.chains[0].id;
}
const accounts = await connector.getAccounts();
const connections = new Map([[connector.uid, {
accounts: [accounts[0]],
chainId,
connector
}]]);
config.setState(state => _objectSpread(_objectSpread({}, state), {}, {
chainId,
connections,
current: connector.uid,
status: "connected"
}));
}
function resetConnectorState(config) {
config._internal.connectors.setState(prev => prev.filter(c => c.id !== WEB3AUTH_CONNECTOR_ID));
config.connectors.filter(c => c.id !== WEB3AUTH_CONNECTOR_ID);
}
async function disconnectWeb3AuthFromWagmi(config) {
var _config$storage3, _config$storage4;
const connector = getWeb3authConnector(config);
await Promise.all([(_config$storage3 = config.storage) === null || _config$storage3 === void 0 ? void 0 : _config$storage3.setItem(`${connector === null || connector === void 0 ? void 0 : connector.id}.disconnected`, true), (_config$storage4 = config.storage) === null || _config$storage4 === void 0 ? void 0 : _config$storage4.removeItem("injected.connected")]);
resetConnectorState(config);
config.setState(state => _objectSpread(_objectSpread({}, state), {}, {
chainId: state.chainId,
connections: new Map(),
current: undefined,
status: "disconnected"
}));
}
const Web3AuthWagmiProvider = defineComponent({
name: "Web3AuthWagmiProvider",
setup() {
const {
isConnected,
connection,
web3Auth,
chainNamespace
} = useWeb3Auth();
const {
disconnect
} = useWeb3AuthDisconnect();
const wagmiConfig = useConfig();
const {
mutate: reconnect
} = useReconnect();
const lastSyncedProvider = shallowRef(null);
const lastSyncedConnectorName = ref(null);
const suppressWagmiDisconnect = ref(false);
useConnectionEffect({
onDisconnect: async () => {
log.info("Disconnected from wagmi");
const isSuppressed = suppressWagmiDisconnect.value;
suppressWagmiDisconnect.value = false;
if (!isSuppressed && isConnected.value) await disconnect();
const connector = getWeb3authConnector(wagmiConfig);
// reset wagmi connector state if the provider handles disconnection because of the accountsChanged event
// from the connected provider
if (connector) {
resetConnectorState(wagmiConfig);
}
}
});
watch([isConnected, connection, chainNamespace], async () => {
var _newConnection$ethere;
const newIsConnected = isConnected.value;
const newConnection = connection.value;
const newEth = (_newConnection$ethere = newConnection === null || newConnection === void 0 ? void 0 : newConnection.ethereumProvider) !== null && _newConnection$ethere !== void 0 ? _newConnection$ethere : null;
const w3aWagmiConnector = getWeb3authConnector(wagmiConfig);
const shouldBindToWagmi = newIsConnected && chainNamespace.value === CHAIN_NAMESPACES.EIP155 && Boolean(newConnection && newEth);
if (shouldBindToWagmi) {
var _web3Auth$value;
const hasSameBinding = lastSyncedProvider.value === newEth && lastSyncedConnectorName.value === newConnection.connectorName && (newConnection === null || newConnection === void 0 ? void 0 : newConnection.connectorName) === ((_web3Auth$value = web3Auth.value) === null || _web3Auth$value === void 0 ? void 0 : _web3Auth$value.connection.connectorName) && wagmiConfig.state.status === "connected";
if (hasSameBinding) {
return;
}
if (!hasSameBinding && w3aWagmiConnector) {
if (wagmiConfig.state.status === "connected") {
suppressWagmiDisconnect.value = true;
await disconnectWeb3AuthFromWagmi(wagmiConfig);
} else {
resetConnectorState(wagmiConfig);
}
}
lastSyncedProvider.value = newEth;
lastSyncedConnectorName.value = newConnection.connectorName;
const connector = setupConnector(newEth, wagmiConfig);
if (!connector) {
throw new Error("Failed to setup connector");
}
await connectWeb3AuthWithWagmi(connector, wagmiConfig);
reconnect();
} else if (!newIsConnected || chainNamespace.value !== CHAIN_NAMESPACES.EIP155) {
lastSyncedProvider.value = null;
lastSyncedConnectorName.value = null;
if (wagmiConfig.state.status === "connected") {
suppressWagmiDisconnect.value = true;
await disconnectWeb3AuthFromWagmi(wagmiConfig);
} else if (w3aWagmiConnector) {
resetConnectorState(wagmiConfig);
}
}
}, {
immediate: true
});
},
render() {
var _this$$slots$default;
return h((_this$$slots$default = this.$slots.default) !== null && _this$$slots$default !== void 0 ? _this$$slots$default : "");
}
});
const Web3AuthWagmiInnerProvider = defineComponent({
name: "Web3AuthWagmiInnerProvider",
props: {
config: {
type: Object,
required: false
}
},
setup(props) {
const {
config
} = props;
provide(configKey, config);
},
render() {
var _this$$slots$default2;
return h(Web3AuthWagmiProvider, {}, (_this$$slots$default2 = this.$slots.default) !== null && _this$$slots$default2 !== void 0 ? _this$$slots$default2 : "");
}
});
const WagmiProvider = defineComponent({
name: "WagmiProvider",
props: {
config: {
type: Object,
required: false
}
},
setup(props) {
const {
config
} = props;
const {
web3Auth,
isInitialized
} = useWeb3Auth();
const finalConfig = shallowRef(defaultWagmiConfig);
const configKey = ref(randomId());
const getTransport = chain => {
const {
wsTarget,
rpcTarget,
fallbackWsTargets = [],
fallbackRpcTargets = []
} = chain;
const transports = [];
if (wsTarget) transports.push(webSocket(wsTarget));
if (fallbackWsTargets.length > 0) transports.push(...fallbackWsTargets.map(target => webSocket(target)));
if (rpcTarget) transports.push(http(rpcTarget));
if (fallbackRpcTargets.length > 0) transports.push(...fallbackRpcTargets.map(target => http(target)));
return fallback(transports);
};
const defineWagmiConfig = () => {
var _web3Auth$value2;
const configParams = _objectSpread(_objectSpread({
ssr: true
}, config), {}, {
chains: undefined,
connectors: [],
transports: {},
multiInjectedProviderDiscovery: false,
client: undefined
});
const wagmiChains = [];
if (isInitialized.value && web3Auth !== null && web3Auth !== void 0 && (_web3Auth$value2 = web3Auth.value) !== null && _web3Auth$value2 !== void 0 && (_web3Auth$value2 = _web3Auth$value2.coreOptions) !== null && _web3Auth$value2 !== void 0 && _web3Auth$value2.chains) {
var _web3Auth$value$curre;
const defaultChainId = (_web3Auth$value$curre = web3Auth.value.currentChain) === null || _web3Auth$value$curre === void 0 ? void 0 : _web3Auth$value$curre.chainId;
const chains = web3Auth.value.coreOptions.chains.filter(chain => chain.chainNamespace === CHAIN_NAMESPACES.EIP155);
if (chains.length === 0) throw WalletInitializationError.invalidParams("No valid chains found in web3auth config for wagmi.");
chains.forEach(chain => {
const wagmiChain = defineChain({
id: Number.parseInt(chain.chainId, 16),
// id in number form
name: chain.displayName,
rpcUrls: {
default: {
http: [chain.rpcTarget],
webSocket: [chain.wsTarget]
}
},
blockExplorers: chain.blockExplorerUrl ? {
default: {
name: "explorer",
// TODO: correct name if chain config has it
url: chain.blockExplorerUrl
}
} : undefined,
nativeCurrency: {
name: chain.tickerName,
symbol: chain.ticker,
decimals: chain.decimals || 18
}
});
if (defaultChainId === chain.chainId) {
wagmiChains.unshift(wagmiChain);
} else {
wagmiChains.push(wagmiChain);
}
configParams.transports[wagmiChain.id] = getTransport(chain);
});
configParams.chains = [wagmiChains[0], ...wagmiChains.slice(1)];
}
return createConfig(configParams);
};
const hydrateWagmiConfig = () => {
if (finalConfig.value) {
hydrate(finalConfig.value, _objectSpread({
reconnectOnMount: false
}, props.config));
}
};
watch(isInitialized, (newIsInitialized, prevIsInitialized) => {
var _web3Auth$value3;
(_web3Auth$value3 = web3Auth.value) === null || _web3Auth$value3 === void 0 || _web3Auth$value3.setAnalyticsProperties({
wagmi_enabled: true
});
if (newIsInitialized && !prevIsInitialized) {
finalConfig.value = defineWagmiConfig();
hydrateWagmiConfig();
configKey.value = randomId();
}
}, {
immediate: true
});
if (!isInitialized.value) {
hydrateWagmiConfig();
}
return {
finalConfig,
configKey
};
},
render() {
return h(Web3AuthWagmiInnerProvider, {
config: this.finalConfig,
key: this.configKey
}, {
default: () => {
var _this$$slots$default3, _this$$slots;
return (_this$$slots$default3 = (_this$$slots = this.$slots).default) === null || _this$$slots$default3 === void 0 ? void 0 : _this$$slots$default3.call(_this$$slots);
}
});
}
});
export { WagmiProvider, connectWeb3AuthWithWagmi, createWeb3AuthConnectorForWagmi, disconnectWeb3AuthFromWagmi, getWeb3authConnector, resetConnectorState, setupConnector };