@biuauth/wallet-connect-v2-adapter
Version:
wallet connect v2 adapter for web3auth
481 lines (476 loc) • 20 kB
JavaScript
import { CHAIN_NAMESPACES, WALLET_ADAPTERS, ADAPTER_NAMESPACES, ADAPTER_CATEGORY, ADAPTER_STATUS, WalletInitializationError, ADAPTER_EVENTS, log, Web3AuthError, WalletLoginError, WalletOperationsError } from '@web3auth/base';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { WalletConnectV2Provider } from '@biuauth/ethereum-provider';
import SignClient from '@walletconnect/sign-client';
import { getSdkError, isValidArray } from '@walletconnect/utils';
import { BaseEvmAdapter } from '@web3auth/base-evm-adapter';
import merge from 'lodash.merge';
const WALLET_CONNECT_EXTENSION_ADAPTERS = [{
name: "Argent",
chains: [CHAIN_NAMESPACES.EIP155],
logo: "https://images.web3auth.io/login-argent.svg",
mobile: {
native: "argent://",
universal: "https://www.argent.xyz/app"
},
desktop: {
native: "",
universal: ""
}
}, {
name: "Trust Wallet",
chains: [CHAIN_NAMESPACES.EIP155],
logo: "https://images.web3auth.io/login-trust.svg",
mobile: {
native: "trust:",
universal: "https://link.trustwallet.com"
},
desktop: {
native: "",
universal: ""
}
}, {
name: "Zerion",
chains: [CHAIN_NAMESPACES.EIP155],
logo: "https://images.web3auth.io/login-zerion.svg",
mobile: {
native: "zerion://",
universal: "https://wallet.zerion.io"
},
desktop: {
native: "",
universal: ""
}
}];
var DEFAULT_EIP155_METHODS;
(function (DEFAULT_EIP155_METHODS) {
DEFAULT_EIP155_METHODS["ETH_SEND_TRANSACTION"] = "eth_sendTransaction";
DEFAULT_EIP155_METHODS["ETH_SIGN"] = "eth_sign";
DEFAULT_EIP155_METHODS["PERSONAL_SIGN"] = "personal_sign";
DEFAULT_EIP155_METHODS["ETH_SIGN_TYPED_DATA"] = "eth_signTypedData";
})(DEFAULT_EIP155_METHODS || (DEFAULT_EIP155_METHODS = {}));
var DEFAULT_EIP_155_EVENTS;
(function (DEFAULT_EIP_155_EVENTS) {
DEFAULT_EIP_155_EVENTS["ETH_CHAIN_CHANGED"] = "chainChanged";
DEFAULT_EIP_155_EVENTS["ETH_ACCOUNTS_CHANGED"] = "accountsChanged";
})(DEFAULT_EIP_155_EVENTS || (DEFAULT_EIP_155_EVENTS = {}));
/**
* Extracts a name for the site from the DOM
*/
const getSiteName = window => {
const {
document
} = window;
const siteName = document.querySelector('head > meta[property="og:site_name"]');
if (siteName) {
return siteName.content;
}
const metaTitle = document.querySelector('head > meta[name="title"]');
if (metaTitle) {
return metaTitle.content;
}
if (document.title && document.title.length > 0) {
return document.title;
}
return window.location.hostname;
};
/**
* Returns whether the given image URL exists
* @param url - the url of the image
* @returns - whether the image exists
*/
function imgExists(url) {
return new Promise((resolve, reject) => {
try {
const img = document.createElement("img");
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src = url;
} catch (e) {
reject(e);
}
});
}
/**
* Extracts an icon for the site from the DOM
*/
async function getSiteIcon(window) {
const {
document
} = window;
// Use the site's favicon if it exists
let icon = document.querySelector('head > link[rel="shortcut icon"]');
if (icon && (await imgExists(icon.href))) {
return icon.href;
}
// Search through available icons in no particular order
icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find(_icon => Boolean(_icon.href)) || null;
if (icon && (await imgExists(icon.href))) {
return icon.href;
}
return null;
}
/**
* Gets site metadata and returns it
*
*/
const getSiteMetadata = async () => ({
name: getSiteName(window),
icon: await getSiteIcon(window)
});
const getNamespacesFromChains = chains => {
const supportedNamespaces = [];
chains.forEach(chainId => {
const [namespace] = chainId.split(":");
if (!supportedNamespaces.includes(namespace)) {
supportedNamespaces.push(namespace);
}
});
return supportedNamespaces;
};
const getSupportedMethodsByNamespace = namespace => {
switch (namespace) {
case CHAIN_NAMESPACES.EIP155:
return Object.values(DEFAULT_EIP155_METHODS);
default:
throw new Error(`No default methods for namespace: ${namespace}`);
}
};
const getSupportedEventsByNamespace = namespace => {
switch (namespace) {
case CHAIN_NAMESPACES.EIP155:
return Object.values(DEFAULT_EIP_155_EVENTS);
default:
throw new Error(`No default events for namespace: ${namespace}`);
}
};
const getRequiredNamespaces = chains => {
const selectedNamespaces = getNamespacesFromChains(chains);
return Object.fromEntries(selectedNamespaces.map(namespace => [namespace, {
methods: getSupportedMethodsByNamespace(namespace),
chains: chains.filter(chain => chain.startsWith(namespace)),
events: getSupportedEventsByNamespace(namespace)
}]));
};
const getWalletConnectV2Settings = async (namespace, chainIds, projectID) => {
if (namespace === CHAIN_NAMESPACES.EIP155) {
const appMetadata = await getSiteMetadata();
const adapterSettings = {
walletConnectInitOptions: {
projectId: projectID,
relayUrl: "wss://relay.walletconnect.com",
metadata: {
name: appMetadata.name,
description: appMetadata.name,
url: window.location.origin,
icons: [appMetadata.icon || ""]
}
}
};
const chainNamespaces = chainIds.map(chainId => {
return `${namespace}:${chainId}`;
});
const loginSettings = {
requiredNamespaces: getRequiredNamespaces(chainNamespaces)
};
return {
adapterSettings,
loginSettings
};
}
throw new Error(`Unsupported chain namespace: ${namespace}`);
};
const isChainIdSupported = (chainNamespace, chainID, loginSettings) => {
var _supportedNamespaces$, _supportedNamespaces$2;
const supportedNamespaces = (loginSettings === null || loginSettings === void 0 ? void 0 : loginSettings.requiredNamespaces) || {};
const wcChainNamespace = `${chainNamespace}:${chainID}`;
if (!supportedNamespaces[chainNamespace].chains || ((_supportedNamespaces$ = supportedNamespaces[chainNamespace].chains) === null || _supportedNamespaces$ === void 0 ? void 0 : _supportedNamespaces$.length) === 0) {
return false;
}
const isSupported = (_supportedNamespaces$2 = supportedNamespaces[chainNamespace].chains) === null || _supportedNamespaces$2 === void 0 ? void 0 : _supportedNamespaces$2.includes(wcChainNamespace);
return !!isSupported;
};
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
class WalletConnectV2Adapter extends BaseEvmAdapter {
constructor() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
super(options);
_defineProperty(this, "name", WALLET_ADAPTERS.WALLET_CONNECT_V2);
_defineProperty(this, "adapterNamespace", ADAPTER_NAMESPACES.EIP155);
_defineProperty(this, "currentChainNamespace", CHAIN_NAMESPACES.EIP155);
_defineProperty(this, "type", ADAPTER_CATEGORY.EXTERNAL);
_defineProperty(this, "adapterOptions", void 0);
_defineProperty(this, "status", ADAPTER_STATUS.NOT_READY);
_defineProperty(this, "adapterData", {
uri: "",
extensionAdapters: WALLET_CONNECT_EXTENSION_ADAPTERS
});
_defineProperty(this, "connector", null);
_defineProperty(this, "activeSession", null);
_defineProperty(this, "wcProvider", null);
this.adapterOptions = _objectSpread({}, options);
}
get connected() {
return !!this.activeSession;
}
get provider() {
if (this.status !== ADAPTER_STATUS.NOT_READY && this.wcProvider) {
return this.wcProvider.provider;
}
return null;
}
set provider(_) {
throw new Error("Not implemented");
}
async init(options) {
var _this$adapterOptions$, _this$chainConfig, _this$chainConfig2;
await super.init();
super.checkInitializationRequirements();
const projectId = (_this$adapterOptions$ = this.adapterOptions.adapterSettings) === null || _this$adapterOptions$ === void 0 || (_this$adapterOptions$ = _this$adapterOptions$.walletConnectInitOptions) === null || _this$adapterOptions$ === void 0 ? void 0 : _this$adapterOptions$.projectId;
if (!projectId) {
throw WalletInitializationError.invalidParams("Wallet connect project id is required in wallet connect v2 adapter");
}
const wc2Settings = await getWalletConnectV2Settings((_this$chainConfig = this.chainConfig) === null || _this$chainConfig === void 0 ? void 0 : _this$chainConfig.chainNamespace, [parseInt((_this$chainConfig2 = this.chainConfig) === null || _this$chainConfig2 === void 0 ? void 0 : _this$chainConfig2.chainId, 16)], projectId);
if (!this.adapterOptions.loginSettings) {
this.adapterOptions.loginSettings = wc2Settings.loginSettings;
}
this.adapterOptions.adapterSettings = merge(wc2Settings.adapterSettings, this.adapterOptions.adapterSettings);
const {
adapterSettings
} = this.adapterOptions;
this.connector = await SignClient.init(adapterSettings === null || adapterSettings === void 0 ? void 0 : adapterSettings.walletConnectInitOptions);
this.wcProvider = new WalletConnectV2Provider({
config: {
chainConfig: this.chainConfig
},
connector: this.connector
});
this.emit(ADAPTER_EVENTS.READY, WALLET_ADAPTERS.WALLET_CONNECT_V2);
this.status = ADAPTER_STATUS.READY;
log.debug("initializing wallet connect v2 adapter");
if (options.autoConnect) {
await this.checkForPersistedSession();
if (this.connected) {
this.rehydrated = true;
try {
await this.onConnectHandler();
} catch (error) {
log.error("wallet auto connect", error);
this.emit(ADAPTER_EVENTS.ERRORED, error);
}
} else {
this.status = ADAPTER_STATUS.NOT_READY;
this.emit(ADAPTER_EVENTS.CACHE_CLEAR);
}
}
}
async connect() {
super.checkConnectionRequirements();
if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet");
try {
// if already connected
if (this.connected) {
await this.onConnectHandler();
return this.provider;
}
if (this.status !== ADAPTER_STATUS.CONNECTING) {
await this.createNewSession();
}
return this.provider;
} catch (error) {
log.error("Wallet connect v2 adapter error while connecting", error);
// ready again to be connected
this.status = ADAPTER_STATUS.READY;
this.rehydrated = true;
this.emit(ADAPTER_EVENTS.ERRORED, error);
const finalError = error instanceof Web3AuthError ? error : WalletLoginError.connectionError(`Failed to login with wallet connect: ${(error === null || error === void 0 ? void 0 : error.message) || ""}`);
throw finalError;
}
}
async addChain(chainConfig) {
var _this$wcProvider;
let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
super.checkAddChainRequirements(chainConfig, init);
if (!isChainIdSupported(this.currentChainNamespace, parseInt(chainConfig.chainId, 16), this.adapterOptions.loginSettings)) {
throw WalletOperationsError.chainIDNotAllowed(`Unsupported chainID: ${chainConfig.chainId}`);
}
await ((_this$wcProvider = this.wcProvider) === null || _this$wcProvider === void 0 ? void 0 : _this$wcProvider.addChain(chainConfig));
this.addChainConfig(chainConfig);
}
async switchChain(params) {
var _this$wcProvider2;
let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
super.checkSwitchChainRequirements(params, init);
if (!isChainIdSupported(this.currentChainNamespace, parseInt(params.chainId, 16), this.adapterOptions.loginSettings)) {
throw WalletOperationsError.chainIDNotAllowed(`Unsupported chainID: ${params.chainId}`);
}
await ((_this$wcProvider2 = this.wcProvider) === null || _this$wcProvider2 === void 0 ? void 0 : _this$wcProvider2.switchChain({
chainId: params.chainId
}));
this.setAdapterSettings({
chainConfig: this.getChainConfig(params.chainId)
});
}
async getUserInfo() {
if (!this.connected) throw WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first");
return {};
}
async disconnect() {
var _this$activeSession, _this$activeSession2;
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
cleanup: false
};
await super.disconnectSession();
const {
cleanup
} = options;
if (!this.connector || !this.connected || !((_this$activeSession = this.activeSession) !== null && _this$activeSession !== void 0 && _this$activeSession.topic)) throw WalletLoginError.notConnectedError("Not connected with wallet");
await this.connector.disconnect({
topic: (_this$activeSession2 = this.activeSession) === null || _this$activeSession2 === void 0 ? void 0 : _this$activeSession2.topic,
reason: getSdkError("USER_DISCONNECTED")
});
this.rehydrated = false;
if (cleanup) {
this.connector = null;
this.status = ADAPTER_STATUS.NOT_READY;
this.wcProvider = null;
} else {
// ready to connect again
this.status = ADAPTER_STATUS.READY;
}
this.activeSession = null;
await super.disconnect();
}
cleanupPendingPairings() {
if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet");
const inactivePairings = this.connector.pairing.getAll({
active: false
});
if (!isValidArray(inactivePairings)) return;
inactivePairings.forEach(pairing => {
if (this.connector) {
this.connector.pairing.delete(pairing.topic, getSdkError("USER_DISCONNECTED"));
}
});
}
async checkForPersistedSession() {
if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter 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() {
let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
forceNewSession: false
};
try {
var _this$activeSession3, _this$adapterOptions;
if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet");
if (!this.adapterOptions.loginSettings) throw WalletInitializationError.notReady("login settings are not set yet");
this.status = ADAPTER_STATUS.CONNECTING;
this.emit(ADAPTER_EVENTS.CONNECTING, {
adapter: WALLET_ADAPTERS.WALLET_CONNECT_V2
});
if (opts.forceNewSession && (_this$activeSession3 = this.activeSession) !== null && _this$activeSession3 !== void 0 && _this$activeSession3.topic) {
var _this$activeSession4;
await this.connector.disconnect({
topic: (_this$activeSession4 = this.activeSession) === null || _this$activeSession4 === void 0 ? void 0 : _this$activeSession4.topic,
reason: getSdkError("USER_DISCONNECTED")
});
}
log.debug("creating new session for web3auth wallet connect");
const {
uri,
approval
} = await this.connector.connect(this.adapterOptions.loginSettings);
const qrcodeModal = (_this$adapterOptions = this.adapterOptions) === null || _this$adapterOptions === void 0 || (_this$adapterOptions = _this$adapterOptions.adapterSettings) === null || _this$adapterOptions === void 0 ? void 0 : _this$adapterOptions.qrcodeModal;
// Open QRCode modal if a URI was returned (i.e. we're not connecting with an existing pairing).
if (uri) {
if (qrcodeModal) {
qrcodeModal.openModal({
uri
});
log.debug("EVENT", "QR Code Modal closed");
this.status = ADAPTER_STATUS.READY;
this.emit(ADAPTER_EVENTS.READY, WALLET_ADAPTERS.WALLET_CONNECT_V2);
} else {
this.updateAdapterData({
uri,
extensionAdapters: WALLET_CONNECT_EXTENSION_ADAPTERS
});
}
}
this.connector.events.once("proposal_expire", args => {
log.info("proposal expired", args);
// Handle proposal expiration
this.createNewSession({
forceNewSession: true
});
});
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();
if (qrcodeModal) {
qrcodeModal.closeModal();
}
} catch (error) {
log.error("error while creating new wallet connect session", error);
this.emit(ADAPTER_EVENTS.ERRORED, error);
throw error;
}
}
async onConnectHandler() {
var _this$adapterOptions$2;
if (!this.connector || !this.wcProvider) throw WalletInitializationError.notReady("Wallet adapter is not ready yet");
if (!this.chainConfig) throw WalletInitializationError.invalidParams("Chain config is not set");
if ((_this$adapterOptions$2 = this.adapterOptions.adapterSettings) !== null && _this$adapterOptions$2 !== void 0 && _this$adapterOptions$2.qrcodeModal) {
this.wcProvider = new WalletConnectV2Provider({
config: {
chainConfig: this.chainConfig,
skipLookupNetwork: true
},
connector: this.connector
});
}
await this.wcProvider.setupProvider(this.connector);
this.subscribeEvents();
this.cleanupPendingPairings();
this.status = ADAPTER_STATUS.CONNECTED;
this.emit(ADAPTER_EVENTS.CONNECTED, {
adapter: WALLET_ADAPTERS.WALLET_CONNECT_V2,
reconnected: this.rehydrated
});
}
subscribeEvents() {
if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet");
this.connector.events.on("session_update", _ref => {
let {
topic,
params
} = _ref;
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();
});
}
}
export { DEFAULT_EIP155_METHODS, DEFAULT_EIP_155_EVENTS, WALLET_CONNECT_EXTENSION_ADAPTERS, WalletConnectV2Adapter, getNamespacesFromChains, getRequiredNamespaces, getSupportedEventsByNamespace, getSupportedMethodsByNamespace, getWalletConnectV2Settings };
//# sourceMappingURL=walletConnectV2Adapter.esm.js.map