UNPKG

@reown/appkit-controllers

Version:

#### 🔗 [Website](https://reown.com/appkit)

365 lines • 15 kB
/* eslint-disable no-console */ import { proxy, ref, subscribe as sub } from 'valtio/vanilla'; import { subscribeKey as subKey } from 'valtio/vanilla/utils'; import { ConstantsUtil as CommonConstantsUtil, ParseUtil } from '@reown/appkit-common'; import { ConnectionControllerUtil } from '../utils/ConnectionControllerUtil.js'; import { ConnectorControllerUtil } from '../utils/ConnectorControllerUtil.js'; import { CoreHelperUtil } from '../utils/CoreHelperUtil.js'; import { StorageUtil } from '../utils/StorageUtil.js'; import { AppKitError, withErrorBoundary } from '../utils/withErrorBoundary.js'; import { AccountController } from './AccountController.js'; import { ChainController } from './ChainController.js'; import { ConnectorController } from './ConnectorController.js'; import { EventsController } from './EventsController.js'; import { ModalController } from './ModalController.js'; import { RouterController } from './RouterController.js'; import { TransactionsController } from './TransactionsController.js'; // -- State --------------------------------------------- // const state = proxy({ connections: new Map(), isSwitchingConnection: false, wcError: false, buffering: false, status: 'disconnected' }); // eslint-disable-next-line init-declarations let wcConnectionPromise; // -- Controller ---------------------------------------- // const controller = { state, subscribe(callback) { return sub(state, () => callback(state)); }, subscribeKey(key, callback) { return subKey(state, key, callback); }, _getClient() { return state._client; }, setClient(client) { state._client = ref(client); }, initialize(adapters) { const storageConnections = StorageUtil.getConnections(); for (const adapter of adapters) { const namespace = adapter.namespace; if (namespace) { const existingConnections = state.connections.get(namespace) ?? []; const storageConnectionsByNamespace = storageConnections[namespace] ?? []; const allConnections = [...existingConnections, ...storageConnectionsByNamespace]; state.connections.set(namespace, allConnections); } } }, async connectWalletConnect() { if (CoreHelperUtil.isTelegram() || (CoreHelperUtil.isSafari() && CoreHelperUtil.isIos())) { if (wcConnectionPromise) { await wcConnectionPromise; wcConnectionPromise = undefined; return; } if (!CoreHelperUtil.isPairingExpired(state?.wcPairingExpiry)) { const link = state.wcUri; state.wcUri = link; return; } wcConnectionPromise = ConnectionController._getClient() ?.connectWalletConnect?.() .catch(() => undefined); ConnectionController.state.status = 'connecting'; await wcConnectionPromise; wcConnectionPromise = undefined; state.wcPairingExpiry = undefined; ConnectionController.state.status = 'connected'; } else { await ConnectionController._getClient()?.connectWalletConnect?.(); } }, async connectExternal(options, chain, setChain = true) { const connectData = await ConnectionController._getClient()?.connectExternal?.(options); if (setChain) { ChainController.setActiveNamespace(chain); } return connectData; }, async reconnectExternal(options) { await ConnectionController._getClient()?.reconnectExternal?.(options); const namespace = options.chain || ChainController.state.activeChain; if (namespace) { ConnectorController.setConnectorId(options.id, namespace); } }, async setPreferredAccountType(accountType, namespace) { ModalController.setLoading(true, ChainController.state.activeChain); const authConnector = ConnectorController.getAuthConnector(); if (!authConnector) { return; } AccountController.setPreferredAccountType(accountType, namespace); await authConnector.provider.setPreferredAccount(accountType); StorageUtil.setPreferredAccountTypes(AccountController.state.preferredAccountTypes ?? { [namespace]: accountType }); await ConnectionController.reconnectExternal(authConnector); ModalController.setLoading(false, ChainController.state.activeChain); EventsController.sendEvent({ type: 'track', event: 'SET_PREFERRED_ACCOUNT_TYPE', properties: { accountType, network: ChainController.state.activeCaipNetwork?.caipNetworkId || '' } }); }, async signMessage(message) { return ConnectionController._getClient()?.signMessage(message); }, parseUnits(value, decimals) { return ConnectionController._getClient()?.parseUnits(value, decimals); }, formatUnits(value, decimals) { return ConnectionController._getClient()?.formatUnits(value, decimals); }, async sendTransaction(args) { return ConnectionController._getClient()?.sendTransaction(args); }, async getCapabilities(params) { return ConnectionController._getClient()?.getCapabilities(params); }, async grantPermissions(params) { return ConnectionController._getClient()?.grantPermissions(params); }, async walletGetAssets(params) { return ConnectionController._getClient()?.walletGetAssets(params) ?? {}; }, async estimateGas(args) { return ConnectionController._getClient()?.estimateGas(args); }, async writeContract(args) { return ConnectionController._getClient()?.writeContract(args); }, async getEnsAddress(value) { return ConnectionController._getClient()?.getEnsAddress(value); }, async getEnsAvatar(value) { return ConnectionController._getClient()?.getEnsAvatar(value); }, checkInstalled(ids) { return ConnectionController._getClient()?.checkInstalled?.(ids) || false; }, resetWcConnection() { state.wcUri = undefined; state.wcPairingExpiry = undefined; state.wcLinking = undefined; state.recentWallet = undefined; state.status = 'disconnected'; TransactionsController.resetTransactions(); StorageUtil.deleteWalletConnectDeepLink(); }, resetUri() { state.wcUri = undefined; state.wcPairingExpiry = undefined; wcConnectionPromise = undefined; }, finalizeWcConnection() { const { wcLinking, recentWallet } = ConnectionController.state; if (wcLinking) { StorageUtil.setWalletConnectDeepLink(wcLinking); } if (recentWallet) { StorageUtil.setAppKitRecent(recentWallet); } EventsController.sendEvent({ type: 'track', event: 'CONNECT_SUCCESS', properties: { method: wcLinking ? 'mobile' : 'qrcode', name: RouterController.state.data?.wallet?.name || 'Unknown' } }); }, setWcBasic(wcBasic) { state.wcBasic = wcBasic; }, setUri(uri) { state.wcUri = uri; state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry(); }, setWcLinking(wcLinking) { state.wcLinking = wcLinking; }, setWcError(wcError) { state.wcError = wcError; state.buffering = false; }, setRecentWallet(wallet) { state.recentWallet = wallet; }, setBuffering(buffering) { state.buffering = buffering; }, setStatus(status) { state.status = status; }, setIsSwitchingConnection(isSwitchingConnection) { state.isSwitchingConnection = isSwitchingConnection; }, async disconnect({ id, namespace } = {}) { try { await ConnectionController._getClient()?.disconnect({ id, chainNamespace: namespace }); } catch (error) { throw new AppKitError('Failed to disconnect', 'INTERNAL_SDK_ERROR', error); } }, setConnections(connections, chainNamespace) { state.connections = new Map(state.connections.set(chainNamespace, connections)); }, async handleAuthAccountSwitch({ address, connection, namespace }) { const smartAccountAddress = connection.accounts.find(c => c.type === 'smartAccount')?.address; const isAddressSmartAccount = smartAccountAddress?.toLowerCase() === address.toLowerCase(); const accountType = isAddressSmartAccount && ConnectorControllerUtil.canSwitchToSmartAccount(namespace) ? 'smartAccount' : 'eoa'; await ConnectionController.setPreferredAccountType(accountType, namespace); }, async handleActiveConnection({ connection, namespace, address }) { const connector = ConnectorController.getConnectorById(connection.connectorId); const isAuthConnector = connection.connectorId === CommonConstantsUtil.CONNECTOR_ID.AUTH; if (!connector) { throw new Error(`No connector found for connection: ${connection.connectorId}`); } const connectData = await ConnectionController.connectExternal({ id: connector.id, type: connector.type, provider: connector.provider, address, chain: namespace }, namespace); if (isAuthConnector && address) { await ConnectionController.handleAuthAccountSwitch({ address, connection, namespace }); } return connectData?.address; }, async handleDisconnectedConnection({ connection, namespace, address, closeModalOnConnect }) { const connector = ConnectorController.getConnectorById(connection.connectorId); const authName = connection.auth?.name?.toLowerCase(); const isAuthConnector = connection.connectorId === CommonConstantsUtil.CONNECTOR_ID.AUTH; const isWCConnector = connection.connectorId === CommonConstantsUtil.CONNECTOR_ID.WALLET_CONNECT; if (!connector) { throw new Error(`No connector found for connection: ${connection.connectorId}`); } let newAddress = undefined; if (isAuthConnector) { if (authName && ConnectorControllerUtil.isSocialProvider(authName)) { const { address: socialAddress } = await ConnectorControllerUtil.connectSocial({ social: authName, closeModalOnConnect, onOpenFarcaster() { ModalController.open({ view: 'ConnectingFarcaster' }); }, onConnect() { RouterController.replace('ProfileWallets'); } }); newAddress = socialAddress; } else { const { address: emailAddress } = await ConnectorControllerUtil.connectEmail({ closeModalOnConnect, onOpen() { ModalController.open({ view: 'EmailLogin' }); }, onConnect() { RouterController.replace('ProfileWallets'); } }); newAddress = emailAddress; } } else if (isWCConnector) { const { address: wcAddress } = await ConnectorControllerUtil.connectWalletConnect({ walletConnect: true, connector, closeModalOnConnect, onOpen(isMobile) { ModalController.open({ view: isMobile ? 'AllWallets' : 'ConnectingWalletConnect' }); }, onConnect() { RouterController.replace('ProfileWallets'); } }); newAddress = wcAddress; } else { const connectData = await ConnectionController.connectExternal({ id: connector.id, type: connector.type, provider: connector.provider, chain: namespace }, namespace); if (connectData) { newAddress = connectData.address; } } if (isAuthConnector && address) { await ConnectionController.handleAuthAccountSwitch({ address, connection, namespace }); } return newAddress; }, async switchConnection({ connection, address, namespace, closeModalOnConnect, onChange }) { // Validate the account switch ConnectionControllerUtil.validateAccountSwitch({ namespace, connection, address }); let currentAddress = undefined; const caipAddress = AccountController.getCaipAddress(namespace); if (caipAddress) { const { address: currentAddressParsed } = ParseUtil.parseCaipAddress(caipAddress); currentAddress = currentAddressParsed; } const status = ConnectionControllerUtil.getConnectionStatus(connection, namespace); switch (status) { case 'connected': case 'active': { const newAddress = await ConnectionController.handleActiveConnection({ connection, namespace, address }); if (currentAddress && newAddress) { const hasSwitchedAccount = newAddress.toLowerCase() !== currentAddress.toLowerCase(); onChange?.({ address: newAddress, namespace, hasSwitchedAccount, hasSwitchedWallet: status === 'active' }); } break; } case 'disconnected': { const newAddress = await ConnectionController.handleDisconnectedConnection({ connection, namespace, address, closeModalOnConnect }); if (newAddress) { onChange?.({ address: newAddress, namespace, hasSwitchedAccount: true, hasSwitchedWallet: true }); } break; } default: throw new Error(`Invalid connection status: ${status}`); } } }; // Export the controller wrapped with our error boundary export const ConnectionController = withErrorBoundary(controller); //# sourceMappingURL=ConnectionController.js.map