@web3auth/wallet-connect-v2-adapter
Version:
wallet connect v2 adapter for web3auth
371 lines (366 loc) • 16.8 kB
JavaScript
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { signChallenge, verifySignedChallenge } from '@toruslabs/base-controllers';
import Client from '@walletconnect/sign-client';
import { getSdkError, isValidArray } from '@walletconnect/utils';
import { BaseAdapter, WALLET_ADAPTERS, ADAPTER_NAMESPACES, CHAIN_NAMESPACES, ADAPTER_CATEGORY, ADAPTER_STATUS, WalletInitializationError, ADAPTER_EVENTS, log, Web3AuthError, WalletLoginError, getSavedToken, checkIfTokenIsExpired, saveToken } from '@web3auth/base';
import base58 from 'bs58';
import deepmerge from 'deepmerge';
import { getWalletConnectV2Settings } from './config.js';
import { WalletConnectV2Provider } from './WalletConnectV2Provider.js';
class WalletConnectV2Adapter extends BaseAdapter {
constructor(options = {}) {
super(options);
_defineProperty(this, "name", WALLET_ADAPTERS.WALLET_CONNECT_V2);
_defineProperty(this, "adapterNamespace", ADAPTER_NAMESPACES.MULTICHAIN);
_defineProperty(this, "currentChainNamespace", CHAIN_NAMESPACES.OTHER);
_defineProperty(this, "type", ADAPTER_CATEGORY.EXTERNAL);
_defineProperty(this, "adapterOptions", {});
_defineProperty(this, "status", ADAPTER_STATUS.NOT_READY);
_defineProperty(this, "adapterData", {
uri: ""
});
_defineProperty(this, "connector", null);
_defineProperty(this, "activeSession", null);
_defineProperty(this, "wcProvider", null);
this.adapterOptions = _objectSpread({}, options);
this.setAdapterSettings(options);
}
get connected() {
return !!this.activeSession;
}
get provider() {
if (this.status !== ADAPTER_STATUS.NOT_READY && this.wcProvider) {
return this.wcProvider;
}
return null;
}
set provider(_) {
throw new Error("Not implemented");
}
async init(options) {
var _this$adapterOptions$, _this$chainConfig, _this$chainConfig2;
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, [(_this$chainConfig2 = this.chainConfig) === null || _this$chainConfig2 === void 0 ? void 0 : _this$chainConfig2.chainId], projectId);
if (!this.adapterOptions.loginSettings || Object.keys(this.adapterOptions.loginSettings).length === 0) {
this.adapterOptions.loginSettings = wc2Settings.loginSettings;
}
this.adapterOptions.adapterSettings = deepmerge(wc2Settings.adapterSettings || {}, this.adapterOptions.adapterSettings || {});
const {
adapterSettings
} = this.adapterOptions;
this.connector = await Client.init(adapterSettings === null || adapterSettings === void 0 ? void 0 : adapterSettings.walletConnectInitOptions);
this.wcProvider = new WalletConnectV2Provider({
clientId: this.clientId,
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) || ""}`, error);
throw finalError;
}
}
// should be called only before initialization.
setAdapterSettings(adapterSettings) {
var _this$adapterOptions$2, _this$adapterOptions, _this$adapterOptions$3, _this$adapterOptions2, _this$adapterOptions$4;
super.setAdapterSettings(adapterSettings);
const {
qrcodeModal,
walletConnectInitOptions
} = (adapterSettings === null || adapterSettings === void 0 ? void 0 : adapterSettings.adapterSettings) || {};
this.adapterOptions = _objectSpread(_objectSpread({}, this.adapterOptions), {}, {
adapterSettings: (_this$adapterOptions$2 = (_this$adapterOptions = this.adapterOptions) === null || _this$adapterOptions === void 0 ? void 0 : _this$adapterOptions.adapterSettings) !== null && _this$adapterOptions$2 !== void 0 ? _this$adapterOptions$2 : {},
loginSettings: (_this$adapterOptions$3 = (_this$adapterOptions2 = this.adapterOptions) === null || _this$adapterOptions2 === void 0 ? void 0 : _this$adapterOptions2.loginSettings) !== null && _this$adapterOptions$3 !== void 0 ? _this$adapterOptions$3 : {}
});
if (qrcodeModal) this.adapterOptions.adapterSettings.qrcodeModal = qrcodeModal;
if (walletConnectInitOptions) this.adapterOptions.adapterSettings.walletConnectInitOptions = _objectSpread(_objectSpread({}, (_this$adapterOptions$4 = this.adapterOptions.adapterSettings.walletConnectInitOptions) !== null && _this$adapterOptions$4 !== void 0 ? _this$adapterOptions$4 : {}), walletConnectInitOptions);
const {
loginSettings
} = adapterSettings;
if (loginSettings) this.adapterOptions.loginSettings = _objectSpread(_objectSpread({}, this.adapterOptions.loginSettings || {}), loginSettings);
}
async addChain(chainConfig, init = false) {
var _this$wcProvider;
super.checkAddChainRequirements(chainConfig, init);
await ((_this$wcProvider = this.wcProvider) === null || _this$wcProvider === void 0 ? void 0 : _this$wcProvider.addChain(chainConfig));
this.addChainConfig(chainConfig);
}
async switchChain(params, init = false) {
var _this$wcProvider2;
super.checkSwitchChainRequirements(params, init);
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(options = {
cleanup: false,
sessionRemovedByWallet: false
}) {
var _this$activeSession, _this$activeSession2;
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");
if (!options.sessionRemovedByWallet) 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;
this.emit(ADAPTER_EVENTS.DISCONNECTED);
}
async authenticateUser() {
if (!this.provider || this.status !== ADAPTER_STATUS.CONNECTED) throw WalletLoginError.notConnectedError();
const {
chainNamespace,
chainId
} = this.chainConfig;
const accounts = await this.provider.request({
method: chainNamespace === CHAIN_NAMESPACES.EIP155 ? "eth_accounts" : "getAccounts"
});
if (accounts && accounts.length > 0) {
const existingToken = getSavedToken(accounts[0], this.name);
if (existingToken) {
const isExpired = 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 signChallenge(payload, chainNamespace);
const signedMessage = await this._getSignedMessage(challenge, accounts, chainNamespace);
const idToken = await verifySignedChallenge(chainNamespace, signedMessage, challenge, this.name, this.sessionTime, this.clientId, this.web3AuthNetwork);
saveToken(accounts[0], this.name, idToken);
return {
idToken
};
}
throw 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 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(opts = {
forceNewSession: false
}) {
try {
var _this$activeSession3, _this$adapterOptions3;
if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet");
if (!this.adapterOptions.loginSettings || Object.keys(this.adapterOptions.loginSettings).length === 0) 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")
});
}
const {
uri,
approval
} = await this.connector.connect(this.adapterOptions.loginSettings);
const qrcodeModal = (_this$adapterOptions3 = this.adapterOptions) === null || _this$adapterOptions3 === void 0 || (_this$adapterOptions3 = _this$adapterOptions3.adapterSettings) === null || _this$adapterOptions3 === void 0 ? void 0 : _this$adapterOptions3.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
});
log.debug("EVENT", "QR Code Modal closed");
this.status = ADAPTER_STATUS.READY;
this.emit(ADAPTER_EVENTS.READY, WALLET_ADAPTERS.WALLET_CONNECT_V2);
} catch (error) {
log.error("unable to open qr code modal");
}
} else {
this.updateAdapterData({
uri
});
}
}
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) {
var _message;
if ((_message = error.message) !== null && _message !== void 0 && _message.toLowerCase().includes("proposal expired")) {
// Retry if adapter status is still connecting
log.info("current adapter status: ", this.status);
if (this.status === ADAPTER_STATUS.CONNECTING) {
log.info("retrying to create new wallet connect session since proposal expired");
return this.createNewSession({
forceNewSession: true
});
}
if (this.status === ADAPTER_STATUS.READY) {
log.info("ignoring proposal expired error since some other adapter is connected");
return;
}
}
log.error("error while creating new wallet connect session", error);
this.emit(ADAPTER_EVENTS.ERRORED, error);
throw error;
}
}
async onConnectHandler() {
var _this$adapterOptions$5;
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");
this.subscribeEvents();
if ((_this$adapterOptions$5 = this.adapterOptions.adapterSettings) !== null && _this$adapterOptions$5 !== void 0 && _this$adapterOptions$5.qrcodeModal) {
this.wcProvider = new WalletConnectV2Provider({
clientId: this.clientId,
config: {
chainConfig: this.chainConfig,
skipLookupNetwork: true
},
connector: this.connector
});
}
await this.wcProvider.setupProvider(this.connector);
this.cleanupPendingPairings();
this.status = ADAPTER_STATUS.CONNECTED;
this.emit(ADAPTER_EVENTS.CONNECTED, {
adapter: WALLET_ADAPTERS.WALLET_CONNECT_V2,
reconnected: this.rehydrated,
provider: this.provider
});
}
subscribeEvents() {
if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter 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 === CHAIN_NAMESPACES.EIP155 ? "personal_sign" : "signMessage",
params: chainNamespace === CHAIN_NAMESPACES.EIP155 ? [challenge, accounts[0]] : {
message: Buffer.from(challenge)
}
});
if (chainNamespace === CHAIN_NAMESPACES.SOLANA) return base58.encode(signedMessage);
return signedMessage;
}
}
export { WalletConnectV2Adapter };