@web3auth/no-modal
Version:
Multi chain wallet aggregator for web3Auth
471 lines (467 loc) • 21.9 kB
JavaScript
'use strict';
var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var baseControllers = require('@toruslabs/base-controllers');
var Client = require('@walletconnect/sign-client');
var utils$2 = require('@walletconnect/utils');
var wsEmbed = require('@web3auth/ws-embed');
var deepmerge = require('deepmerge');
var analytics = require('../../base/analytics.js');
var IChainInterface = require('../../base/chain/IChainInterface.js');
var baseConnector = require('../../base/connector/baseConnector.js');
var constants = require('../../base/connector/constants.js');
require('@web3auth/auth');
var utils$1 = require('../../base/connector/utils.js');
var index$1 = require('../../base/errors/index.js');
var loglevel = require('../../base/loglevel.js');
require('../../base/plugin/errors.js');
require('../../base/plugin/IPlugin.js');
var utils = require('../../base/utils.js');
var index = require('../../base/wallet/index.js');
var config = require('./config.js');
var WalletConnectV2Provider = require('./WalletConnectV2Provider.js');
class WalletConnectV2Connector extends baseConnector.BaseConnector {
constructor(options) {
var _this$connectorOption, _this$connectorOption2, _this$connectorOption3, _this$connectorOption4, _this$connectorOption5;
super(options);
_defineProperty(this, "name", index.WALLET_CONNECTORS.WALLET_CONNECT_V2);
_defineProperty(this, "connectorNamespace", IChainInterface.CONNECTOR_NAMESPACES.MULTICHAIN);
_defineProperty(this, "currentChainNamespace", baseControllers.CHAIN_NAMESPACES.OTHER);
_defineProperty(this, "type", constants.CONNECTOR_CATEGORY.EXTERNAL);
_defineProperty(this, "connectorOptions", void 0);
_defineProperty(this, "status", constants.CONNECTOR_STATUS.NOT_READY);
_defineProperty(this, "connectorData", {
uri: ""
});
_defineProperty(this, "connector", null);
_defineProperty(this, "activeSession", null);
_defineProperty(this, "wcProvider", null);
_defineProperty(this, "analytics", void 0);
this.connectorOptions = _objectSpread({}, options);
const {
qrcodeModal,
walletConnectInitOptions
} = (options === null || options === void 0 ? void 0 : options.connectorSettings) || {};
this.connectorOptions = _objectSpread(_objectSpread({}, this.connectorOptions), {}, {
connectorSettings: (_this$connectorOption = (_this$connectorOption2 = this.connectorOptions) === null || _this$connectorOption2 === void 0 ? void 0 : _this$connectorOption2.connectorSettings) !== null && _this$connectorOption !== void 0 ? _this$connectorOption : {},
loginSettings: (_this$connectorOption3 = (_this$connectorOption4 = this.connectorOptions) === null || _this$connectorOption4 === void 0 ? void 0 : _this$connectorOption4.loginSettings) !== null && _this$connectorOption3 !== void 0 ? _this$connectorOption3 : {}
});
this.analytics = options.analytics;
if (qrcodeModal) this.connectorOptions.connectorSettings.qrcodeModal = qrcodeModal;
if (walletConnectInitOptions) this.connectorOptions.connectorSettings.walletConnectInitOptions = _objectSpread(_objectSpread({}, (_this$connectorOption5 = this.connectorOptions.connectorSettings.walletConnectInitOptions) !== null && _this$connectorOption5 !== void 0 ? _this$connectorOption5 : {}), walletConnectInitOptions);
const {
loginSettings
} = options;
if (loginSettings) this.connectorOptions.loginSettings = _objectSpread(_objectSpread({}, this.connectorOptions.loginSettings || {}), loginSettings);
}
get connected() {
return !!this.activeSession;
}
get provider() {
if (this.status !== constants.CONNECTOR_STATUS.NOT_READY && this.wcProvider) {
return this.wcProvider;
}
return null;
}
set provider(_) {
throw new Error("Not implemented");
}
async init(options) {
var _this$connectorOption6;
const chainConfig = this.coreOptions.chains.find(x => x.chainId === options.chainId);
super.checkInitializationRequirements({
chainConfig
});
const projectId = (_this$connectorOption6 = this.connectorOptions.connectorSettings) === null || _this$connectorOption6 === void 0 || (_this$connectorOption6 = _this$connectorOption6.walletConnectInitOptions) === null || _this$connectorOption6 === void 0 ? void 0 : _this$connectorOption6.projectId;
const filteredChains = this.coreOptions.chains.filter(x => x.chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 || x.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA);
if (filteredChains.length === 0) {
throw index$1.WalletInitializationError.invalidParams("No supported chains found");
}
const wc2Settings = await config.getWalletConnectV2Settings(filteredChains, projectId);
if (!this.connectorOptions.loginSettings || Object.keys(this.connectorOptions.loginSettings).length === 0) {
this.connectorOptions.loginSettings = wc2Settings.loginSettings;
}
this.connectorOptions.connectorSettings = deepmerge(wc2Settings.connectorSettings || {}, this.connectorOptions.connectorSettings || {});
const {
connectorSettings
} = this.connectorOptions;
this.connector = await Client.init(connectorSettings === null || connectorSettings === void 0 ? void 0 : connectorSettings.walletConnectInitOptions);
this.wcProvider = new WalletConnectV2Provider.WalletConnectV2Provider({
config: {
chain: chainConfig,
chains: this.coreOptions.chains
},
connector: this.connector
});
this.emit(constants.CONNECTOR_EVENTS.READY, index.WALLET_CONNECTORS.WALLET_CONNECT_V2);
this.status = constants.CONNECTOR_STATUS.READY;
loglevel.log.debug("initializing wallet connect v2 connector");
if (options.autoConnect) {
await this.checkForPersistedSession();
if (this.connected) {
this.rehydrated = true;
try {
await this.onConnectHandler({
chain: chainConfig
});
} catch (error) {
loglevel.log.error("wallet auto connect", error);
this.emit(constants.CONNECTOR_EVENTS.REHYDRATION_ERROR, error);
}
} else {
this.status = constants.CONNECTOR_STATUS.NOT_READY;
this.emit(constants.CONNECTOR_EVENTS.CACHE_CLEAR);
}
}
}
async connect({
chainId
}) {
super.checkConnectionRequirements();
const chainConfig = this.coreOptions.chains.find(x => x.chainId === chainId);
if (!chainConfig) throw index$1.WalletLoginError.connectionError("Chain config is not available");
if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet");
// Skip tracking for rehydration since only new connections are tracked
// Track when connection completes since it auto-initializes to generate QR code
const shouldTrack = !this.connected && !this.rehydrated;
const startTime = Date.now();
let eventData = {
connector: this.name,
connector_type: this.type,
is_injected: this.isInjected,
is_wallet_connect: true,
chain_id: utils.getCaipChainId(chainConfig),
chain_name: chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.displayName,
chain_namespace: chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.chainNamespace
};
try {
const trackCompletionEvents = () => {
// track connection events
if (shouldTrack) {
var _this$activeSession, _this$analytics, _this$analytics2;
const {
name,
url,
redirect
} = ((_this$activeSession = this.activeSession) === null || _this$activeSession === void 0 || (_this$activeSession = _this$activeSession.peer) === null || _this$activeSession === void 0 ? void 0 : _this$activeSession.metadata) || {};
const {
native,
universal,
linkMode
} = redirect || {};
eventData = _objectSpread(_objectSpread({}, eventData), {}, {
connector: name || this.name,
wallet_url: url,
redirect_native: native,
redirect_universal: universal,
redirect_link_mode_enabled: linkMode
});
(_this$analytics = this.analytics) === null || _this$analytics === void 0 || _this$analytics.track(analytics.ANALYTICS_EVENTS.CONNECTION_STARTED, eventData);
(_this$analytics2 = this.analytics) === null || _this$analytics2 === void 0 || _this$analytics2.track(analytics.ANALYTICS_EVENTS.CONNECTION_COMPLETED, _objectSpread(_objectSpread({}, eventData), {}, {
duration: Date.now() - startTime
}));
}
};
// if already connected
if (this.connected) {
await this.onConnectHandler({
chain: chainConfig
});
return this.provider;
}
if (this.status !== constants.CONNECTOR_STATUS.CONNECTING) {
await this.createNewSession({
chainConfig,
trackCompletionEvents
});
}
return this.provider;
} catch (error) {
loglevel.log.error("Wallet connect v2 connector error while connecting", error);
// ready again to be connected
this.status = constants.CONNECTOR_STATUS.READY;
this.rehydrated = true;
this.emit(constants.CONNECTOR_EVENTS.ERRORED, error);
// track connection events
if (shouldTrack) {
var _this$analytics3, _this$analytics4;
(_this$analytics3 = this.analytics) === null || _this$analytics3 === void 0 || _this$analytics3.track(analytics.ANALYTICS_EVENTS.CONNECTION_STARTED, eventData);
(_this$analytics4 = this.analytics) === null || _this$analytics4 === void 0 || _this$analytics4.track(analytics.ANALYTICS_EVENTS.CONNECTION_FAILED, _objectSpread(_objectSpread(_objectSpread({}, eventData), baseControllers.getErrorAnalyticsProperties(error)), {}, {
duration: Date.now() - startTime
}));
}
const finalError = error instanceof index$1.Web3AuthError ? error : index$1.WalletLoginError.connectionError(`Failed to login with wallet connect: ${(error === null || error === void 0 ? void 0 : error.message) || ""}`, error);
throw finalError;
}
}
async switchChain(params, init = false) {
super.checkSwitchChainRequirements(params, init);
if (!this.wcProvider) throw index$1.WalletInitializationError.notReady("Wallet Connect provider is not ready yet");
try {
await this.wcProvider.switchChain({
chainId: params.chainId
});
} catch (error) {
loglevel.log.error("error while switching chain", error);
throw error;
}
}
async getUserInfo() {
if (!this.connected) throw index$1.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first");
return {};
}
async disconnect(options = {
cleanup: false,
sessionRemovedByWallet: false
}) {
var _this$activeSession2, _this$activeSession3;
const {
cleanup
} = options;
if (!this.connector || !this.connected || !((_this$activeSession2 = this.activeSession) !== null && _this$activeSession2 !== void 0 && _this$activeSession2.topic)) throw index$1.WalletLoginError.notConnectedError("Not connected with wallet");
if (!options.sessionRemovedByWallet) await this.connector.disconnect({
topic: (_this$activeSession3 = this.activeSession) === null || _this$activeSession3 === void 0 ? void 0 : _this$activeSession3.topic,
reason: utils$2.getSdkError("USER_DISCONNECTED")
});
this.rehydrated = false;
if (cleanup) {
this.connector = null;
this.status = constants.CONNECTOR_STATUS.NOT_READY;
this.wcProvider = null;
} else {
// ready to connect again
this.status = constants.CONNECTOR_STATUS.READY;
}
this.activeSession = null;
this.emit(constants.CONNECTOR_EVENTS.DISCONNECTED);
}
async getIdentityToken() {
if (!this.provider || this.status !== constants.CONNECTOR_STATUS.CONNECTED) throw index$1.WalletLoginError.notConnectedError();
const {
chainId
} = this.provider;
const currentChainConfig = this.coreOptions.chains.find(x => x.chainId === chainId);
if (!currentChainConfig) throw index$1.WalletLoginError.connectionError("Chain config is not available");
const {
chainNamespace
} = currentChainConfig;
const accounts = await this.provider.request({
method: chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 ? wsEmbed.EVM_METHOD_TYPES.GET_ACCOUNTS : wsEmbed.SOLANA_METHOD_TYPES.GET_ACCOUNTS
});
if (accounts && accounts.length > 0) {
const existingToken = utils$1.getSavedToken(accounts[0], this.name);
if (existingToken) {
const isExpired = utils$1.checkIfTokenIsExpired(existingToken);
if (!isExpired) {
return {
idToken: existingToken
};
}
}
const payload = {
domain: window.location.origin,
uri: window.location.href,
address: accounts[0],
chainId: parseInt(chainId, 16),
version: "1",
nonce: Math.random().toString(36).slice(2),
issuedAt: new Date().toISOString()
};
const challenge = await baseControllers.signChallenge(payload, chainNamespace);
const signedMessage = await this._getSignedMessage(challenge, accounts, chainNamespace);
const idToken = await baseControllers.verifySignedChallenge(chainNamespace, signedMessage, challenge, this.name, this.coreOptions.sessionTime, this.coreOptions.clientId, this.coreOptions.web3AuthNetwork);
utils$1.saveToken(accounts[0], this.name, idToken);
return {
idToken
};
}
throw index$1.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first");
}
async enableMFA() {
throw new Error("Method Not implemented");
}
async manageMFA() {
throw new Error("Method Not implemented");
}
cleanupPendingPairings() {
if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet");
const inactivePairings = this.connector.pairing.getAll({
active: false
});
if (!utils$2.isValidArray(inactivePairings)) return;
inactivePairings.forEach(pairing => {
if (this.connector) {
this.connector.pairing.delete(pairing.topic, utils$2.getSdkError("USER_DISCONNECTED"));
}
});
}
async checkForPersistedSession() {
if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet");
if (this.connector.session.length) {
const lastKeyIndex = this.connector.session.keys.length - 1;
this.activeSession = this.connector.session.get(this.connector.session.keys[lastKeyIndex]);
}
return this.activeSession;
}
async createNewSession({
forceNewSession = false,
chainConfig,
trackCompletionEvents
}) {
try {
var _this$activeSession4, _this$connectorOption7;
if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet");
if (!this.connectorOptions.loginSettings || Object.keys(this.connectorOptions.loginSettings).length === 0) throw index$1.WalletInitializationError.notReady("login settings are not set yet");
this.status = constants.CONNECTOR_STATUS.CONNECTING;
this.emit(constants.CONNECTOR_EVENTS.CONNECTING, {
connector: index.WALLET_CONNECTORS.WALLET_CONNECT_V2
});
if (forceNewSession && (_this$activeSession4 = this.activeSession) !== null && _this$activeSession4 !== void 0 && _this$activeSession4.topic) {
var _this$activeSession5;
await this.connector.disconnect({
topic: (_this$activeSession5 = this.activeSession) === null || _this$activeSession5 === void 0 ? void 0 : _this$activeSession5.topic,
reason: utils$2.getSdkError("USER_DISCONNECTED")
});
}
const {
uri,
approval
} = await this.connector.connect(this.connectorOptions.loginSettings);
const qrcodeModal = (_this$connectorOption7 = this.connectorOptions) === null || _this$connectorOption7 === void 0 || (_this$connectorOption7 = _this$connectorOption7.connectorSettings) === null || _this$connectorOption7 === void 0 ? void 0 : _this$connectorOption7.qrcodeModal;
// Open QRCode modal if a URI was returned (i.e. we're not connecting with an existing pairing).
if (uri) {
if (qrcodeModal) {
try {
await qrcodeModal.openModal({
uri
});
loglevel.log.debug("EVENT", "QR Code Modal closed");
this.status = constants.CONNECTOR_STATUS.READY;
this.emit(constants.CONNECTOR_EVENTS.READY, index.WALLET_CONNECTORS.WALLET_CONNECT_V2);
} catch (error) {
loglevel.log.error("unable to open qr code modal", error);
}
} else {
this.updateConnectorData({
uri
});
}
}
loglevel.log.info("awaiting session approval from wallet");
// Await session approval from the wallet.
const session = await approval();
this.activeSession = session;
// Handle the returned session (e.g. update UI to "connected" state).
await this.onConnectHandler({
chain: chainConfig,
trackCompletionEvents
});
if (qrcodeModal) {
qrcodeModal.closeModal();
}
} catch (error) {
var _error$message;
if ((_error$message = error.message) !== null && _error$message !== void 0 && _error$message.toLowerCase().includes("proposal expired")) {
// Retry if connector status is still connecting
loglevel.log.info("current connector status: ", this.status);
if (this.status === constants.CONNECTOR_STATUS.CONNECTING) {
loglevel.log.info("retrying to create new wallet connect session since proposal expired");
return this.createNewSession({
forceNewSession: true,
chainConfig
});
}
if (this.status === constants.CONNECTOR_STATUS.READY) {
loglevel.log.info("ignoring proposal expired error since some other connector is connected");
return;
}
}
loglevel.log.error("error while creating new wallet connect session", error);
this.emit(constants.CONNECTOR_EVENTS.ERRORED, error);
throw error;
}
}
async onConnectHandler({
chain,
trackCompletionEvents
}) {
var _this$connectorOption8;
if (!this.connector || !this.wcProvider) throw index$1.WalletInitializationError.notReady("Wallet connect connector is not ready yet");
this.subscribeEvents();
if ((_this$connectorOption8 = this.connectorOptions.connectorSettings) !== null && _this$connectorOption8 !== void 0 && _this$connectorOption8.qrcodeModal) {
this.wcProvider = new WalletConnectV2Provider.WalletConnectV2Provider({
config: {
chain,
chains: this.coreOptions.chains,
skipLookupNetwork: true
},
connector: this.connector
});
}
await this.wcProvider.setupProvider(this.connector);
this.cleanupPendingPairings();
this.status = constants.CONNECTOR_STATUS.CONNECTED;
// track connection events
if (trackCompletionEvents) trackCompletionEvents();
this.emit(constants.CONNECTOR_EVENTS.CONNECTED, {
connector: index.WALLET_CONNECTORS.WALLET_CONNECT_V2,
reconnected: this.rehydrated,
provider: this.provider
});
}
subscribeEvents() {
if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet");
this.connector.events.on("session_update", ({
topic,
params
}) => {
if (!this.connector) return;
const {
namespaces
} = params;
const _session = this.connector.session.get(topic);
// Overwrite the `namespaces` of the existing session with the incoming one.
const updatedSession = _objectSpread(_objectSpread({}, _session), {}, {
namespaces
});
// Integrate the updated session state into your dapp state.
this.activeSession = updatedSession;
});
this.connector.events.on("session_delete", () => {
// Session was deleted -> reset the dapp state, clean up from user session, etc.
this.disconnect({
sessionRemovedByWallet: true
});
});
}
async _getSignedMessage(challenge, accounts, chainNamespace) {
const signedMessage = await this.provider.request({
method: chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 ? wsEmbed.EVM_METHOD_TYPES.PERSONAL_SIGN : wsEmbed.SOLANA_METHOD_TYPES.SIGN_MESSAGE,
params: chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 ? [challenge, accounts[0]] : {
data: challenge
}
});
return signedMessage;
}
}
const walletConnectV2Connector = params => {
return ({
coreOptions,
projectConfig,
analytics
}) => {
var _params$walletConnect;
const projectId = (params === null || params === void 0 || (_params$walletConnect = params.walletConnectInitOptions) === null || _params$walletConnect === void 0 ? void 0 : _params$walletConnect.projectId) || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.walletConnectProjectId);
const connectorSettings = _objectSpread(_objectSpread({}, params), {}, {
walletConnectInitOptions: _objectSpread(_objectSpread({}, params === null || params === void 0 ? void 0 : params.walletConnectInitOptions), {}, {
projectId: projectId
})
});
return new WalletConnectV2Connector({
connectorSettings,
coreOptions,
analytics
});
};
};
exports.walletConnectV2Connector = walletConnectV2Connector;