UNPKG

@cosmos-kit/core

Version:

cosmos-kit wallet connector core package

433 lines (432 loc) 20.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WalletManager = void 0; const cosmiframe_1 = require("@dao-dao/cosmiframe"); const bowser_1 = __importDefault(require("bowser")); const events_1 = __importDefault(require("events")); const bases_1 = require("./bases"); const cosmiframe_2 = require("./cosmiframe"); const constants_1 = require("./cosmiframe/constants"); const repository_1 = require("./repository"); const types_1 = require("./types"); const utils_1 = require("./utils"); class WalletManager extends bases_1.StateBase { chainRecords = []; walletRepos = []; defaultNameService = 'icns'; mainWallets = []; coreEmitter; walletConnectOptions; session; repelWallet = true; // only allow one wallet type to connect at one time. i.e. you cannot connect keplr and cosmostation at the same time isLazy; // stands for `globalIsLazy` setting throwErrors; subscribeConnectEvents; cosmiframeEnabled; _reconnectMap = {}; constructor(chains, wallets, logger, throwErrors, subscribeConnectEvents = true, allowedCosmiframeParentOrigins = [ /^https?:\/\/localhost(:\d+)?/, /^https:\/\/(.+\.)?osmosis\.zone/, /^https:\/\/(.+\.)?daodao\.zone/, /^https:\/\/.+-da0da0\.vercel\.app/, /^https:\/\/(.+\.)?abstract\.money/, ], assetLists, defaultNameService, walletConnectOptions, signerOptions, endpointOptions, sessionOptions) { super(); this.throwErrors = throwErrors; this.subscribeConnectEvents = subscribeConnectEvents; this.coreEmitter = new events_1.default(); this.logger = logger; if (defaultNameService) this.defaultNameService = defaultNameService; this.session = new utils_1.Session({ duration: 1800000, callback: () => { this.mainWallets.forEach((w) => w.disconnectAll(false)); window?.localStorage.removeItem('cosmos-kit@2:core//accounts'); window?.localStorage.removeItem('cosmos-kit@2:core//current-wallet'); }, ...sessionOptions, }); this.walletConnectOptions = walletConnectOptions; this.cosmiframeEnabled = (0, cosmiframe_1.isInIframe)() && !!allowedCosmiframeParentOrigins?.length; // Add Cosmiframe wallet to beginning of list if enabled. wallets = [ ...(this.cosmiframeEnabled ? [(0, cosmiframe_2.makeCosmiframeWallet)(allowedCosmiframeParentOrigins)] : []), ...wallets, ]; wallets.forEach(({ walletName }) => (this._reconnectMap[walletName] = () => this._reconnect(walletName, true))); this.init(chains, assetLists, wallets, walletConnectOptions, signerOptions, endpointOptions); } init(chains, assetLists, wallets, walletConnectOptions, signerOptions, endpointOptions) { this.logger.info(`${chains.length} chains and ${wallets.length} wallets are provided!`); this.isLazy = endpointOptions?.isLazy; this.chainRecords = chains.map((chain) => { const chainName = typeof chain === 'string' ? chain : chain.chain_name; const converted = (0, utils_1.convertChain)(chain, assetLists, signerOptions, endpointOptions?.endpoints?.[chainName], this.isLazy, this.logger); return converted; }); this.mainWallets = wallets.map((wallet) => { wallet.logger = this.logger; wallet.throwErrors = this.throwErrors; wallet.session = this.session; wallet.walletConnectOptions = this.walletConnectOptions; wallet?.setChains(this.chainRecords); return wallet; }); this.chainRecords.forEach((chainRecord, index) => { const repo = new repository_1.WalletRepo(chainRecord, wallets.map(({ getChainWallet }) => getChainWallet(chainRecord.name))); repo.logger = this.logger; repo.repelWallet = this.repelWallet; repo.session = this.session; this.walletRepos.push(repo); if (repo.fetchInfo) { this.chainRecords[index] = repo.chainRecord; } }); this.checkEndpoints(endpointOptions?.endpoints); } checkEndpoints(endpoints) { Object.keys(endpoints || {}).map((key) => { if (this.chainRecords.findIndex((c) => c.name === key) === -1) { this.logger?.warn(`You are providing endpointOptions with unrecognized chain NAME ${key} (NOT found such chain in ChainProvider property "chains")`); } }); } setWalletRepel(value) { this.repelWallet = value; this.walletRepos.forEach((repo) => (repo.repelWallet = value)); window?.localStorage.setItem('cosmos-kit@2:core//repel-wallet', value.toString()); } addEndpoints = (endpoints) => { this.mainWallets.forEach((mainWallet) => { mainWallet.addEnpoints(endpoints); }); }; addChains = (chains, assetLists, signerOptions, endpoints) => { const newChainRecords = chains.map((chain) => { const chainName = typeof chain === 'string' ? chain : chain.chain_name; return (0, utils_1.convertChain)(chain, assetLists, signerOptions, endpoints?.[chainName], this.isLazy, this.logger); }); newChainRecords.forEach((chainRecord) => { const index = this.chainRecords.findIndex((chainRecord2) => chainRecord2.name === chainRecord.name); if (index == -1) { this.chainRecords.push(chainRecord); } else { this.chainRecords[index] = chainRecord; } }); this.checkEndpoints(endpoints); this.mainWallets.forEach((wallet) => { wallet.setChains(newChainRecords, false); }); newChainRecords.forEach((chainRecord, i) => { const repo = new repository_1.WalletRepo(chainRecord, this.mainWallets.map(({ getChainWallet }) => getChainWallet(chainRecord.name))); repo.setActions({ viewOpen: this.actions?.viewOpen, viewWalletRepo: this.actions?.viewWalletRepo, }); repo.wallets.forEach((w) => { w.setActions({ data: this.actions?.data, state: this.actions?.state, message: this.actions?.message, }); }); repo.logger = this.logger; repo.repelWallet = this.repelWallet; repo.session = this.session; if (repo.fetchInfo) { this.chainRecords[i] = repo.chainRecord; } const index = this.walletRepos.findIndex((repo2) => repo2.chainName === repo.chainName); if (index == -1) { this.walletRepos.push(repo); } else { this.walletRepos[index] = repo; } }); }; on = (event, handler) => { this.coreEmitter.on(event, handler); }; off = (event, handler) => { this.coreEmitter.off(event, handler); }; get activeRepos() { return this.walletRepos.filter((repo) => repo.isActive === true); } getMainWallet = (walletName) => { const wallet = this.mainWallets.find((w) => w.walletName === walletName); if (!wallet) { throw new utils_1.WalletNotProvidedError(walletName); } return wallet; }; getWalletRepo = (chainName) => { const walletRepo = this.walletRepos.find((wr) => wr.chainName === chainName); if (!walletRepo) { throw new Error(`Chain ${chainName} is not provided.`); } return walletRepo; }; getChainWallet = (chainName, walletName) => { const chainWallet = this.getMainWallet(walletName).getChainWallet(chainName); if (!chainWallet) { throw new Error(`${chainName} is not provided!`); } return chainWallet; }; getChainRecord = (chainName) => { const chainRecord = this.chainRecords.find((c) => c.name === chainName); if (!chainRecord) { throw new Error(`${chainName} is not provided!`); } return chainRecord; }; // get chain logo getChainLogo = (chainName) => { const chainRecord = this.getChainRecord(chainName); return ( // until chain_registry fix this // chainRecord?.chain.logo_URIs?.svg || // chainRecord?.chain.logo_URIs?.png || // chainRecord?.chain.logo_URIs?.jpeg || chainRecord?.assetList?.assets[0]?.logo_URIs?.svg || chainRecord?.assetList?.assets[0]?.logo_URIs?.png || undefined); }; getNameService = async (chainName) => { let _chainName; if (!chainName) { if (!this.defaultNameService) { throw new Error('defaultNameService is undefined'); } const { getNameServiceRegistryFromName } = await Promise.resolve().then(() => __importStar(require('./utils'))); const registry = getNameServiceRegistryFromName(this.defaultNameService); if (!registry) { throw new Error('Unknown defaultNameService ' + this.defaultNameService); } _chainName = registry.chainName; } else { _chainName = chainName; } return await this.getWalletRepo(_chainName).getNameService(); }; _reconnect = async (walletName, checkConnection = false, onlyChainNames) => { if (checkConnection && this.getMainWallet(walletName)?.isWalletDisconnected) { return; } this.logger?.debug('[Event Emit] `refresh_connection` (manager)'); this.coreEmitter.emit('refresh_connection'); await this.getMainWallet(walletName).connect(); await Promise.all(this.getMainWallet(walletName) .getChainWalletList(true) .map((w, index) => !onlyChainNames ? // If no chains specified, just connect the first chain wallet and sync to all active chain wallets. index === 0 ? w.connect(true) : undefined : // If chains specified, connect only the specified chains, without sync. onlyChainNames.includes(w.chainName) ? w.connect(false) : undefined)); }; _restoreAccounts = async () => { const walletName = // If Cosmiframe enabled, use it by default instead of stored wallet. this.cosmiframeEnabled ? constants_1.COSMIFRAME_WALLET_ID : window.localStorage.getItem('cosmos-kit@2:core//current-wallet'); if (walletName) { try { const mainWallet = this.getMainWallet(walletName); mainWallet.activate(); let onlyChainNames; if (mainWallet.clientMutable.state === types_1.State.Done) { const accountsStr = window.localStorage.getItem('cosmos-kit@2:core//accounts'); if (accountsStr && accountsStr !== '[]') { const accounts = JSON.parse(accountsStr); const accountChainWallets = accounts.flatMap((data) => { const chainWallet = mainWallet .getChainWalletList(false) .find((w) => w.chainRecord.chain?.chain_id === data.chainId && w.namespace === data.namespace); chainWallet?.activate(); if (mainWallet.walletInfo.mode === 'wallet-connect') { chainWallet?.setData(data); chainWallet?.setState(types_1.State.Done); } return chainWallet || []; }); mainWallet.setState(types_1.State.Done); // Activate all repos for the account chains and get their chain // records. const connectedChainRecords = accountChainWallets.flatMap((chainWallet) => { try { const repo = this.getWalletRepo(chainWallet.chainName); repo.activate(); return repo.chainRecord; } catch { return []; } }); const attemptConnectAll = async () => { try { await mainWallet.client?.enable?.(connectedChainRecords.map((chainRecord) => chainRecord.chain.chain_id)); return true; } catch (error) { // If the error is a wallet rejection, log and return false to // indicate user rejection. Do not throw to continue. if (error instanceof Error && mainWallet.rejectMatched(error)) { this.logger?.error(`Failed to connect to stored chains: ${error.message}`); return false; } throw error; } }; // Connect to all chains at once. On error (other than user // rejection), attempt to add each chain and try again. try { const rejected = !(await attemptConnectAll()); // If user rejected, do not call `_reconnect` at the bottom or // else every chain wallet will attempt to connect to all chains // again. if (rejected) { return; } } catch (error) { // If failed to enable, add each chain and try again. for (const chainRecord of connectedChainRecords) { await mainWallet.client?.addChain?.(chainRecord); } const rejected = !(await attemptConnectAll()); // If user rejected, do not call `_reconnect` at the bottom or // else every chain wallet will attempt to connect to all chains // again. if (rejected) { return; } } onlyChainNames = connectedChainRecords.map((chainRecord) => chainRecord.name); } } if (mainWallet.walletInfo.mode !== 'wallet-connect') { await this._reconnect(walletName, undefined, onlyChainNames); } } catch (error) { if (error instanceof utils_1.WalletNotProvidedError) { this.logger?.warn(error.message); } else { throw error; } } } }; _handleCosmiframeKeystoreChangeEvent = (event) => { if (typeof event.data === 'object' && 'event' in event.data && event.data.event === constants_1.COSMIFRAME_KEYSTORECHANGE_EVENT) { // Dispatch event to our window. window.dispatchEvent(new Event(constants_1.COSMIFRAME_KEYSTORECHANGE_EVENT)); // Reconnect if the parent updates. this._reconnect(constants_1.COSMIFRAME_WALLET_ID); } }; onMounted = async () => { if (typeof window === 'undefined') return; // If Cosmiframe enabled, rebroadcast keystore change event messages as // events and reconnect if the parent changes. Since the outer window can be // a different origin (and it most likely is), it cannot dispatch events on // our (the iframe's) window. Thus, it posts a message with the event name // to our window and we broadcast it. if (this.cosmiframeEnabled) { window.addEventListener('message', this._handleCosmiframeKeystoreChangeEvent); } const parser = bowser_1.default.getParser(window.navigator.userAgent); const env = { browser: parser.getBrowserName(true), device: (parser.getPlatform().type || 'desktop'), os: parser.getOSName(true), }; this.setEnv(env); this.walletRepos.forEach((repo) => repo.setEnv(env)); await Promise.all(this.mainWallets.map(async (wallet) => { wallet.setEnv(env); wallet.emitter?.emit('broadcast_env', env); if (this.subscribeConnectEvents) { wallet.walletInfo.connectEventNamesOnWindow?.forEach((eventName) => { window.addEventListener(eventName, this._reconnectMap[wallet.walletName]); this.logger?.debug(`Add "${eventName}" event listener to window`); }); wallet.walletInfo.connectEventNamesOnClient?.forEach(async (eventName) => { wallet.client?.on?.(eventName, this._reconnectMap[wallet.walletName]); this.logger?.debug(`Add "${eventName}" event listener to wallet client ${wallet.walletPrettyName}`); }); } if (wallet.walletInfo.mode === 'wallet-connect') { await wallet.initClient(this.walletConnectOptions); } else { await wallet.initClient(); } })); await this._restoreAccounts(); }; onUnmounted = () => { if (typeof window === 'undefined') { return; } // If using Cosmiframe, stop listening for keystore change event. if (this.cosmiframeEnabled) { window.removeEventListener('message', this._handleCosmiframeKeystoreChangeEvent); } this.mainWallets.forEach((wallet) => { wallet.walletInfo.connectEventNamesOnWindow?.forEach((eventName) => { window.removeEventListener(eventName, this._reconnectMap[wallet.walletName]); }); wallet.walletInfo.connectEventNamesOnClient?.forEach(async (eventName) => { wallet.client?.off?.(eventName, this._reconnectMap[wallet.walletName]); }); }); }; } exports.WalletManager = WalletManager;