UNPKG

@web3modal/base

Version:

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

490 lines • 21.9 kB
import { Connection } from '@solana/web3.js'; import { ApiController, AssetController, CoreHelperUtil, EventsController, NetworkController, OptionsController } from '@web3modal/core'; import { ConstantsUtil, PresetsUtil } from '@web3modal/scaffold-utils'; import { ConstantsUtil as CommonConstantsUtil } from '@web3modal/common'; import { SolConstantsUtil, SolHelpersUtil, SolStoreUtil } from '@web3modal/scaffold-utils/solana'; import { PublicKey } from '@solana/web3.js'; import UniversalProvider, {} from '@walletconnect/universal-provider'; import { watchStandard } from './utils/watchStandard.js'; import { WalletConnectProvider } from './providers/WalletConnectProvider.js'; import { AuthProvider } from './providers/AuthProvider.js'; import { W3mFrameHelpers, W3mFrameProvider, W3mFrameRpcConstants } from '@web3modal/wallet'; import { withSolanaNamespace } from './utils/withSolanaNamespace.js'; import { SafeLocalStorage } from '../../../utils/SafeLocalStorage.js'; import { createSendTransaction } from './utils/createSendTransaction.js'; import { CoinbaseWalletProvider } from './providers/CoinbaseWalletProvider.js'; export class SolanaWeb3JsClient { constructor(options) { this.appKit = undefined; this.instanceOptions = undefined; this.options = undefined; this.hasSyncedConnectedAccount = false; this.chain = CommonConstantsUtil.CHAIN.SOLANA; this.availableProviders = []; const { solanaConfig, chains, defaultChain, connectionSettings = 'confirmed' } = options; if (!solanaConfig) { throw new Error('web3modal:constructor - solanaConfig is undefined'); } this.instanceOptions = options; this.chains = chains; this.connectionSettings = connectionSettings; this.defaultChain = SolHelpersUtil.getChainFromCaip(this.chains, SafeLocalStorage.getItem(SolConstantsUtil.CAIP_CHAIN_ID) || withSolanaNamespace(defaultChain?.chainId) || withSolanaNamespace(chains[0]?.chainId)); this.networkControllerClient = { switchCaipNetwork: async (caipNetwork) => { if (caipNetwork) { try { await this.switchNetwork(caipNetwork); } catch (error) { SolStoreUtil.setError(error); } } }, getApprovedCaipNetworksData: async () => { if (SolStoreUtil.state.provider) { const approvedCaipNetworkIds = SolStoreUtil.state.provider.chains.map(chain => `solana:${chain.chainId}`); return Promise.resolve({ approvedCaipNetworkIds, supportsAllNetworks: false }); } return Promise.resolve({ approvedCaipNetworkIds: undefined, supportsAllNetworks: false }); } }; this.connectionControllerClient = { connectWalletConnect: async (onUri) => { const wcProvider = this.availableProviders.find(provider => provider.type === 'WALLET_CONNECT'); if (!wcProvider || !(wcProvider instanceof WalletConnectProvider)) { throw new Error('connectionControllerClient:getWalletConnectUri - provider is undefined'); } wcProvider.onUri = onUri; return this.setProvider(wcProvider); }, connectExternal: async ({ id }) => { const externalProvider = this.availableProviders.find(provider => provider.name.toLocaleLowerCase() === id.toLocaleLowerCase()); if (!externalProvider) { throw Error('connectionControllerClient:connectExternal - adapter was undefined'); } return this.setProvider(externalProvider); }, disconnect: async () => { await SolStoreUtil.state.provider?.disconnect(); }, signMessage: async (message) => { const provider = SolStoreUtil.state.provider; if (!provider) { throw new Error('connectionControllerClient:signMessage - provider is undefined'); } const signature = await provider.signMessage(new TextEncoder().encode(message)); return new TextDecoder().decode(signature); }, estimateGas: async (params) => { if (params.chainNamespace !== 'solana') { throw new Error('Chain namespace is not supported'); } const connection = SolStoreUtil.state.connection; if (!connection) { throw new Error('Connection is not set'); } const provider = this.getProvider(); const transaction = await createSendTransaction({ provider, connection, to: '11111111111111111111111111111111', value: 1 }); const fee = await transaction.getEstimatedFee(connection); return BigInt(fee || 0); }, getEnsAvatar: async (value) => await Promise.resolve(value), getEnsAddress: async (value) => await Promise.resolve(value), writeContract: async () => await Promise.resolve('0x'), sendTransaction: async (params) => { if (params.chainNamespace !== 'solana') { throw new Error('Chain namespace is not supported'); } const connection = SolStoreUtil.state.connection; const address = SolStoreUtil.state.address; if (!connection || !address) { throw new Error('Connection is not set'); } const provider = this.getProvider(); const transaction = await createSendTransaction({ provider, connection, to: params.to, value: params.value }); const result = await provider.sendTransaction(transaction, connection); await new Promise(resolve => { const interval = setInterval(async () => { const status = await connection.getSignatureStatus(result); if (status?.value) { clearInterval(interval); resolve(); } }, 1000); }); await this.syncBalance(address); return result; }, parseUnits: () => BigInt(0), formatUnits: () => '', checkInstalled: (ids = []) => { const availableIds = new Set(this.availableProviders.map(provider => provider.name)); return ids.some(id => availableIds.has(id)); } }; } construct(appKit, options) { const { projectId } = options; const clientOptions = this.instanceOptions; if (!clientOptions) { throw new Error('Solana:construct - clientOptions is undefined'); } this.appKit = appKit; this.options = options; const { chains } = clientOptions; if (!projectId) { throw new Error('Solana:construct - projectId is undefined'); } this.initializeProviders({ relayUrl: SolConstantsUtil.UNIVERSAL_PROVIDER_RELAY_URL, metadata: clientOptions.metadata, projectId: options.projectId, ...clientOptions.solanaConfig.auth }); if (this.defaultChain) { this.appKit?.setCaipNetwork(this.defaultChain); SolStoreUtil.setCaipChainId(this.defaultChain.id); const chain = this.chains.find(c => withSolanaNamespace(c.chainId) === this.defaultChain.id); if (chain) { SolStoreUtil.setCurrentChain(chain); } } this.syncNetwork(); this.syncRequestedNetworks(chains, this.options?.chainImages); SolStoreUtil.subscribeKey('address', () => { this.syncAccount(); }); SolStoreUtil.subscribeKey('caipChainId', () => { this.syncNetwork(); }); SolStoreUtil.subscribeKey('isConnected', isConnected => { this.appKit?.setIsConnected(isConnected, 'solana'); }); AssetController.subscribeNetworkImages(() => { this.syncNetwork(); }); NetworkController.subscribeKey('caipNetwork', (newCaipNetwork) => { const newChain = chains.find(_chain => _chain.chainId === newCaipNetwork?.id.split(':')[1]); if (!newChain) { return; } if (NetworkController.state.caipNetwork && !SolStoreUtil.state.isConnected) { SolStoreUtil.setCaipChainId(`solana:${newChain.chainId}`); SolStoreUtil.setCurrentChain(newChain); SafeLocalStorage.setItem(SolConstantsUtil.CAIP_CHAIN_ID, `solana:${newChain.chainId}`); ApiController.reFetchWallets(); } }); EventsController.subscribe(state => { if (state.data.event === 'SELECT_WALLET') { const isMobile = CoreHelperUtil.isMobile(); const isClient = CoreHelperUtil.isClient(); if (isMobile && isClient) { if (state.data.properties?.name === 'Phantom' && !('phantom' in window)) { const href = window.location.href; const protocol = href.startsWith('https') ? 'https' : 'http'; const host = href.split('/')[2]; const ref = `${protocol}://${host}`; window.location.href = `https://phantom.app/ul/browse/${href}?ref=${ref}`; } if (state.data.properties?.name === 'Coinbase Wallet' && !('coinbaseSolana' in window)) { const href = window.location.href; window.location.href = `https://go.cb-w.com/dapp?cb_url=${href}`; } } } }); } disconnect() { return this.getProvider().disconnect(); } getAddress() { return SolStoreUtil.state.address; } getWalletProvider() { return SolStoreUtil.state.provider; } getWalletProviderType() { return SolStoreUtil.state.provider?.type; } getWalletConnection() { return SolStoreUtil.state.connection; } syncConnectedWalletInfo() { const currentActiveWallet = SafeLocalStorage.getItem(SolConstantsUtil.WALLET_ID); const provider = SolStoreUtil.state.provider; if (provider?.type === 'WALLET_CONNECT') { const wcProvider = provider; if (wcProvider.session) { this.appKit?.setConnectedWalletInfo({ ...wcProvider.session.peer.metadata, name: wcProvider.session.peer.metadata.name, icon: wcProvider.session.peer.metadata.icons?.[0] }, this.chain); } } else if (currentActiveWallet) { this.appKit?.setConnectedWalletInfo({ name: currentActiveWallet }, this.chain); } } async syncAccount() { const address = SolStoreUtil.state.address; const chainId = SolStoreUtil.state.currentChain?.chainId; if (address && chainId) { const caipAddress = `${ConstantsUtil.INJECTED_CONNECTOR_ID}:${chainId}:${address}`; this.appKit?.setCaipAddress(caipAddress, this.chain); await this.syncBalance(address); this.syncConnectedWalletInfo(); this.hasSyncedConnectedAccount = true; } else if (this.hasSyncedConnectedAccount) { this.appKit?.resetWcConnection(); this.appKit?.resetNetwork(); this.appKit?.resetAccount('solana'); } } async syncBalance(address) { if (!SolStoreUtil.state.connection) { throw new Error('Connection is not set'); } if (!SolStoreUtil.state.currentChain) { throw new Error('Chain is not set'); } const balance = (await SolStoreUtil.state.connection.getBalance(new PublicKey(address))) / SolConstantsUtil.LAMPORTS_PER_SOL; this.appKit?.setBalance(balance.toString(), SolStoreUtil.state.currentChain.currency, this.chain); } syncRequestedNetworks(chains, chainImages) { const requestedCaipNetworks = chains?.map(chain => ({ id: `solana:${chain.chainId}`, name: chain.name, imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], imageUrl: chainImages?.[chain.chainId], chain: this.chain })); this.appKit?.setRequestedCaipNetworks(requestedCaipNetworks ?? [], this.chain); } async switchNetwork(caipNetwork) { const caipChainId = caipNetwork.id; if (this.provider instanceof AuthProvider) { await this.provider.switchNetwork(caipChainId); } const chain = SolHelpersUtil.getChainFromCaip(this.chains, caipChainId); SolStoreUtil.setCaipChainId(chain.id); SolStoreUtil.setCurrentChain(chain); SafeLocalStorage.setItem(SolConstantsUtil.CAIP_CHAIN_ID, chain.id); await this.syncNetwork(); await this.syncAccount(); } async syncNetwork() { const chainImages = this.options?.chainImages; const address = SolStoreUtil.state.address; const storeChainId = SolStoreUtil.state.caipChainId; if (this.chains) { const chain = SolHelpersUtil.getChainFromCaip(this.chains, storeChainId); if (chain) { const caipChainId = `solana:${chain.chainId}`; SolStoreUtil.setConnection(new Connection(SolHelpersUtil.detectRpcUrl(chain, OptionsController.state.projectId), this.connectionSettings)); this.appKit?.setCaipNetwork({ id: caipChainId, name: chain.name, imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], imageUrl: chainImages?.[chain.chainId], chain: this.chain }); if (address) { if (chain.explorerUrl) { const url = `${chain.explorerUrl}/account/${address}`; this.appKit?.setAddressExplorerUrl(url, this.chain); } else { this.appKit?.setAddressExplorerUrl(undefined, this.chain); } if (this.hasSyncedConnectedAccount) { await this.syncBalance(address); } } } } } subscribeProvider(callback) { return SolStoreUtil.subscribe(callback); } async setProvider(provider) { try { if (provider.type === 'AUTH') { this.appKit?.setLoading(true); } const address = await provider.connect(); const connectionChain = provider.chains.find(chain => chain.chainId === SolStoreUtil.state.currentChain?.chainId) || provider.chains[0]; if (!connectionChain) { provider.disconnect(); throw new Error('The wallet does not support any of the required chains'); } SolStoreUtil.setAddress(address); await this.switchNetwork(SolHelpersUtil.getChainFromCaip(this.chains, `solana:${connectionChain.chainId}`)); SolStoreUtil.setProvider(provider); this.provider = provider; SafeLocalStorage.setItem(SolConstantsUtil.WALLET_ID, provider.name); await this.appKit?.setApprovedCaipNetworksData(this.chain); this.watchProvider(provider); SolStoreUtil.setIsConnected(true); } finally { this.appKit?.setLoading(false); } } watchProvider(provider) { const rpcRequestHandler = (request) => { if (!this.appKit) { return; } if (W3mFrameHelpers.checkIfRequestExists(request)) { if (!W3mFrameHelpers.checkIfRequestIsSafe(request)) { this.appKit.handleUnsafeRPCRequest(); } } else { this.appKit.open(); console.error(W3mFrameRpcConstants.RPC_METHOD_NOT_ALLOWED_MESSAGE, { method: request.method }); setTimeout(() => { this.appKit?.showErrorMessage(W3mFrameRpcConstants.RPC_METHOD_NOT_ALLOWED_UI_MESSAGE); }, 300); } }; const rpcSuccessHandler = (_response) => { if (!this.appKit) { return; } if (this.appKit.isTransactionStackEmpty()) { this.appKit.close(); } else { this.appKit.popTransactionStack(); } }; const rpcErrorHandler = (_error) => { if (!this.appKit) { return; } if (this.appKit.isOpen()) { if (this.appKit.isTransactionStackEmpty()) { this.appKit.close(); } else { this.appKit.popTransactionStack(true); } } }; function disconnectHandler() { SafeLocalStorage.removeItem(SolConstantsUtil.WALLET_ID); SolStoreUtil.reset(); provider.removeListener('disconnect', disconnectHandler); provider.removeListener('accountsChanged', accountsChangedHandler); provider.removeListener('connect', accountsChangedHandler); provider.removeListener('auth_rpcRequest', rpcRequestHandler); provider.removeListener('auth_rpcSuccess', rpcSuccessHandler); provider.removeListener('auth_rpcError', rpcErrorHandler); } function accountsChangedHandler(publicKey) { const currentAccount = publicKey.toBase58(); if (currentAccount) { SolStoreUtil.setAddress(currentAccount); } else { SafeLocalStorage.removeItem(SolConstantsUtil.WALLET_ID); SolStoreUtil.reset(); } } provider.on('disconnect', disconnectHandler); provider.on('accountsChanged', accountsChangedHandler); provider.on('connect', accountsChangedHandler); provider.on('auth_rpcRequest', rpcRequestHandler); provider.on('auth_rpcSuccess', rpcSuccessHandler); provider.on('auth_rpcError', rpcErrorHandler); } getProvider() { if (!this.provider) { throw new Error('Provider is not set'); } return this.provider; } async initializeProviders(opts) { if (CoreHelperUtil.isClient()) { this.addProvider(new WalletConnectProvider({ provider: await UniversalProvider.init(opts), chains: this.chains, getActiveChain: () => SolStoreUtil.state.currentChain })); if (opts.email || opts.socials?.length) { if (!opts.projectId) { throw new Error('projectId is required for AuthProvider'); } this.addProvider(new AuthProvider({ provider: new W3mFrameProvider(opts.projectId, withSolanaNamespace(SolStoreUtil.state.currentChain?.chainId)), getActiveChain: () => SolStoreUtil.state.currentChain, auth: { email: opts.email, socials: opts.socials, showWallets: opts.showWallets, walletFeatures: opts.walletFeatures }, chains: this.chains })); } if ('coinbaseSolana' in window) { this.addProvider(new CoinbaseWalletProvider({ provider: window.coinbaseSolana, chains: this.chains, getActiveChain: () => SolStoreUtil.state.currentChain })); } watchStandard(standardAdapters => this.addProvider.bind(this)(...standardAdapters)); } } addProvider(...providers) { const activeProviderName = SafeLocalStorage.getItem(SolConstantsUtil.WALLET_ID); for (const provider of providers) { this.availableProviders = this.availableProviders.filter(p => p.name !== provider.name); this.availableProviders.push(provider); if (provider.name === activeProviderName) { this.setProvider(provider); } } this.syncConnectors(); } syncConnectors() { const connectors = this.availableProviders.map(provider => ({ id: provider.name, type: provider.type, imageUrl: provider.icon, name: provider.name, provider: provider.type === 'AUTH' ? provider : undefined, chain: CommonConstantsUtil.CHAIN.SOLANA, ...provider.auth })); this.appKit?.setConnectors(connectors); } } //# sourceMappingURL=client.js.map